react-smart-image-viewer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,480 @@
1
+ # react-smart-image-viewer
2
+
3
+ <p align="center">
4
+ <strong>A high-performance, TypeScript-first React image viewer with zoom, pan, keyboard, and mobile gesture support.</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="#features">Features</a> •
9
+ <a href="#installation">Installation</a> •
10
+ <a href="#quick-start">Quick Start</a> •
11
+ <a href="#api">API</a> •
12
+ <a href="#examples">Examples</a> •
13
+ <a href="#accessibility">Accessibility</a>
14
+ </p>
15
+
16
+ ---
17
+
18
+ ## Why react-smart-image-viewer?
19
+
20
+ Modern web applications need image viewers that are:
21
+
22
+ - **Fast** - No unnecessary re-renders, optimized with `requestAnimationFrame`
23
+ - **Accessible** - Full keyboard support, ARIA labels, focus management
24
+ - **Mobile-friendly** - Touch gestures, pinch-to-zoom, swipe navigation
25
+ - **Flexible** - Controlled/uncontrolled modes, headless-friendly API
26
+ - **Type-safe** - Built with TypeScript from the ground up
27
+ - **Next.js ready** - SSR-safe, no hydration mismatches
28
+
29
+ This package solves these problems with a lightweight (~15KB gzipped), tree-shakable solution.
30
+
31
+ ## Features
32
+
33
+ ✨ **Modal/Lightbox**
34
+ - Open images in a fullscreen overlay
35
+ - Close via ESC key, overlay click, or close button
36
+ - Prevents body scroll when open
37
+ - Smooth animations
38
+
39
+ 🔍 **Zoom & Pan**
40
+ - Mouse wheel zoom (zooms toward cursor)
41
+ - Button controls for zoom in/out/reset
42
+ - Double-click to zoom in, double-click again to reset
43
+ - Drag to pan when zoomed
44
+ - Pinch-to-zoom on mobile devices
45
+
46
+ 🖼️ **Gallery Support**
47
+ - Single image or array of images
48
+ - Next/Previous navigation with arrows
49
+ - Keyboard navigation (← →)
50
+ - Optional loop mode
51
+ - Image counter display
52
+
53
+ ⌨️ **Keyboard Shortcuts**
54
+ - `ESC` - Close viewer
55
+ - `←` / `→` - Navigate images
56
+ - `+` / `=` - Zoom in
57
+ - `-` - Zoom out
58
+ - `0` - Reset zoom
59
+
60
+ ♿ **Accessibility**
61
+ - `role="dialog"` with `aria-modal`
62
+ - Focus trap inside modal
63
+ - ARIA labels on all interactive elements
64
+ - Screen reader announcements for gallery position
65
+ - Respects `prefers-reduced-motion`
66
+
67
+ ## Installation
68
+
69
+ ```bash
70
+ npm install react-smart-image-viewer
71
+ ```
72
+
73
+ ```bash
74
+ yarn add react-smart-image-viewer
75
+ ```
76
+
77
+ ```bash
78
+ pnpm add react-smart-image-viewer
79
+ ```
80
+
81
+ ## Quick Start
82
+
83
+ ```tsx
84
+ import { ImageViewer } from 'react-smart-image-viewer';
85
+ import 'react-smart-image-viewer/styles.css';
86
+
87
+ function App() {
88
+ const [isOpen, setIsOpen] = useState(false);
89
+
90
+ return (
91
+ <>
92
+ <button onClick={() => setIsOpen(true)}>Open Image</button>
93
+
94
+ <ImageViewer
95
+ images="https://example.com/image.jpg"
96
+ isOpen={isOpen}
97
+ onClose={() => setIsOpen(false)}
98
+ />
99
+ </>
100
+ );
101
+ }
102
+ ```
103
+
104
+ ## API
105
+
106
+ ### `<ImageViewer />` Props
107
+
108
+ | Prop | Type | Default | Description |
109
+ |------|------|---------|-------------|
110
+ | `images` | `string \| ImageSource \| Array` | *required* | Image(s) to display |
111
+ | `isOpen` | `boolean` | - | Controlled open state |
112
+ | `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
113
+ | `initialIndex` | `number` | `0` | Starting image index for gallery |
114
+ | `onClose` | `() => void` | - | Called when viewer should close |
115
+ | `onIndexChange` | `(index: number) => void` | - | Called when image index changes |
116
+ | `zoomStep` | `number` | `0.5` | Zoom increment per step |
117
+ | `minZoom` | `number` | `0.5` | Minimum zoom level |
118
+ | `maxZoom` | `number` | `4` | Maximum zoom level |
119
+ | `showControls` | `boolean` | `true` | Show zoom controls |
120
+ | `showNavigation` | `boolean` | `true` | Show prev/next arrows |
121
+ | `showCounter` | `boolean` | `true` | Show image counter |
122
+ | `closeOnOverlayClick` | `boolean` | `true` | Close on overlay click |
123
+ | `closeOnEscape` | `boolean` | `true` | Close on ESC key |
124
+ | `enableKeyboardNavigation` | `boolean` | `true` | Enable ←/→ navigation |
125
+ | `loop` | `boolean` | `false` | Loop gallery navigation |
126
+ | `className` | `string` | - | Custom overlay class |
127
+ | `imageClassName` | `string` | - | Custom image class |
128
+ | `animationDuration` | `number` | `200` | Animation duration (ms) |
129
+ | `ariaLabel` | `string` | `'Image viewer'` | Accessible label |
130
+ | `renderControls` | `(props) => ReactNode` | - | Custom controls renderer |
131
+ | `renderNavigation` | `(props) => ReactNode` | - | Custom navigation renderer |
132
+
133
+ ### `ImageSource` Type
134
+
135
+ ```typescript
136
+ interface ImageSource {
137
+ src: string; // Image URL (required)
138
+ alt?: string; // Alt text for accessibility
139
+ thumbnail?: string; // Thumbnail URL (for gallery previews)
140
+ title?: string; // Title to display
141
+ }
142
+ ```
143
+
144
+ ### `useImageViewer` Hook
145
+
146
+ A hook for programmatic control of the viewer.
147
+
148
+ ```tsx
149
+ import { useImageViewer, ImageViewer } from 'react-smart-image-viewer';
150
+
151
+ function Gallery() {
152
+ const images = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
153
+
154
+ const viewer = useImageViewer({
155
+ totalImages: images.length,
156
+ loop: true,
157
+ });
158
+
159
+ return (
160
+ <>
161
+ <div className="thumbnails">
162
+ {images.map((img, i) => (
163
+ <img
164
+ key={i}
165
+ src={img}
166
+ onClick={() => viewer.open(i)}
167
+ alt={`Thumbnail ${i + 1}`}
168
+ />
169
+ ))}
170
+ </div>
171
+
172
+ <ImageViewer images={images} {...viewer.getViewerProps()} />
173
+ </>
174
+ );
175
+ }
176
+ ```
177
+
178
+ #### Hook Options
179
+
180
+ | Option | Type | Default | Description |
181
+ |--------|------|---------|-------------|
182
+ | `defaultOpen` | `boolean` | `false` | Initial open state |
183
+ | `defaultIndex` | `number` | `0` | Initial image index |
184
+ | `totalImages` | `number` | `1` | Total number of images |
185
+ | `zoomStep` | `number` | `0.5` | Zoom increment |
186
+ | `minZoom` | `number` | `0.5` | Minimum zoom |
187
+ | `maxZoom` | `number` | `4` | Maximum zoom |
188
+ | `loop` | `boolean` | `false` | Loop navigation |
189
+ | `onOpenChange` | `(isOpen: boolean) => void` | - | Open state callback |
190
+ | `onIndexChange` | `(index: number) => void` | - | Index change callback |
191
+
192
+ #### Hook Return Value
193
+
194
+ ```typescript
195
+ interface UseImageViewerReturn {
196
+ isOpen: boolean;
197
+ open: (index?: number) => void;
198
+ close: () => void;
199
+ toggle: () => void;
200
+ currentIndex: number;
201
+ setCurrentIndex: (index: number) => void;
202
+ goToNext: () => void;
203
+ goToPrevious: () => void;
204
+ zoom: number;
205
+ zoomIn: () => void;
206
+ zoomOut: () => void;
207
+ resetZoom: () => void;
208
+ setZoom: (zoom: number) => void;
209
+ getViewerProps: () => Partial<ImageViewerProps>;
210
+ }
211
+ ```
212
+
213
+ ## Examples
214
+
215
+ ### Single Image
216
+
217
+ ```tsx
218
+ <ImageViewer
219
+ images="https://example.com/photo.jpg"
220
+ isOpen={isOpen}
221
+ onClose={() => setIsOpen(false)}
222
+ />
223
+ ```
224
+
225
+ ### Gallery with Metadata
226
+
227
+ ```tsx
228
+ const images = [
229
+ {
230
+ src: 'https://example.com/photo1.jpg',
231
+ alt: 'Mountain landscape',
232
+ title: 'Swiss Alps',
233
+ },
234
+ {
235
+ src: 'https://example.com/photo2.jpg',
236
+ alt: 'Ocean sunset',
237
+ title: 'Pacific Coast',
238
+ },
239
+ ];
240
+
241
+ <ImageViewer
242
+ images={images}
243
+ isOpen={isOpen}
244
+ onClose={() => setIsOpen(false)}
245
+ initialIndex={0}
246
+ loop
247
+ />
248
+ ```
249
+
250
+ ### Controlled Mode
251
+
252
+ ```tsx
253
+ function ControlledExample() {
254
+ const [isOpen, setIsOpen] = useState(false);
255
+ const [currentIndex, setCurrentIndex] = useState(0);
256
+
257
+ return (
258
+ <ImageViewer
259
+ images={images}
260
+ isOpen={isOpen}
261
+ onClose={() => setIsOpen(false)}
262
+ initialIndex={currentIndex}
263
+ onIndexChange={setCurrentIndex}
264
+ />
265
+ );
266
+ }
267
+ ```
268
+
269
+ ### Uncontrolled Mode
270
+
271
+ ```tsx
272
+ <ImageViewer
273
+ images={images}
274
+ defaultOpen={true}
275
+ onClose={() => console.log('Viewer closed')}
276
+ />
277
+ ```
278
+
279
+ ### Custom Controls
280
+
281
+ ```tsx
282
+ <ImageViewer
283
+ images={images}
284
+ isOpen={isOpen}
285
+ onClose={() => setIsOpen(false)}
286
+ renderControls={({ zoomIn, zoomOut, resetZoom, currentZoom, close }) => (
287
+ <div className="my-controls">
288
+ <button onClick={zoomOut}>−</button>
289
+ <span>{Math.round(currentZoom * 100)}%</span>
290
+ <button onClick={zoomIn}>+</button>
291
+ <button onClick={resetZoom}>Reset</button>
292
+ <button onClick={close}>×</button>
293
+ </div>
294
+ )}
295
+ />
296
+ ```
297
+
298
+ ### Custom Navigation
299
+
300
+ ```tsx
301
+ <ImageViewer
302
+ images={images}
303
+ isOpen={isOpen}
304
+ onClose={() => setIsOpen(false)}
305
+ renderNavigation={({ goToPrevious, goToNext, currentIndex, totalImages }) => (
306
+ <div className="my-nav">
307
+ <button onClick={goToPrevious}>Previous</button>
308
+ <span>{currentIndex + 1} of {totalImages}</span>
309
+ <button onClick={goToNext}>Next</button>
310
+ </div>
311
+ )}
312
+ />
313
+ ```
314
+
315
+ ### With Next.js
316
+
317
+ The component is fully SSR-safe and works with Next.js out of the box:
318
+
319
+ ```tsx
320
+ // pages/gallery.tsx or app/gallery/page.tsx
321
+ 'use client'; // Required for app directory
322
+
323
+ import { useState } from 'react';
324
+ import { ImageViewer } from 'react-smart-image-viewer';
325
+ import 'react-smart-image-viewer/styles.css';
326
+
327
+ export default function GalleryPage() {
328
+ const [isOpen, setIsOpen] = useState(false);
329
+ const [selectedIndex, setSelectedIndex] = useState(0);
330
+
331
+ const images = ['/image1.jpg', '/image2.jpg', '/image3.jpg'];
332
+
333
+ return (
334
+ <main>
335
+ <div className="grid">
336
+ {images.map((src, i) => (
337
+ <img
338
+ key={src}
339
+ src={src}
340
+ onClick={() => {
341
+ setSelectedIndex(i);
342
+ setIsOpen(true);
343
+ }}
344
+ alt={`Gallery image ${i + 1}`}
345
+ />
346
+ ))}
347
+ </div>
348
+
349
+ <ImageViewer
350
+ images={images}
351
+ isOpen={isOpen}
352
+ onClose={() => setIsOpen(false)}
353
+ initialIndex={selectedIndex}
354
+ onIndexChange={setSelectedIndex}
355
+ />
356
+ </main>
357
+ );
358
+ }
359
+ ```
360
+
361
+ ## Accessibility
362
+
363
+ This component follows WAI-ARIA best practices for modal dialogs:
364
+
365
+ ### Semantic Structure
366
+ - Uses `role="dialog"` with `aria-modal="true"`
367
+ - Close button has `aria-label="Close image viewer"`
368
+ - Navigation buttons have descriptive labels
369
+ - Images include alt text support
370
+
371
+ ### Keyboard Support
372
+ - **Tab** - Cycles through focusable elements
373
+ - **Shift+Tab** - Cycles backwards
374
+ - **Escape** - Closes the viewer
375
+ - **Arrow keys** - Navigate gallery
376
+ - Focus is trapped within the modal when open
377
+ - Focus returns to trigger element on close
378
+
379
+ ### Screen Readers
380
+ - Announces "Image X of Y" when navigating
381
+ - Alt text is announced for each image
382
+ - Live regions announce state changes
383
+
384
+ ### Motion
385
+ - Respects `prefers-reduced-motion` media query
386
+ - Animations are disabled for users who prefer reduced motion
387
+
388
+ ## Performance
389
+
390
+ ### Optimizations
391
+
392
+ 1. **requestAnimationFrame** - Zoom and pan operations are throttled using rAF
393
+ 2. **Lazy loading** - Images load on-demand with loading indicators
394
+ 3. **CSS transforms** - Hardware-accelerated transforms for smooth animations
395
+ 4. **Minimal re-renders** - Memoized callbacks and optimized state updates
396
+ 5. **Tree-shakable** - Import only what you need
397
+
398
+ ### Bundle Size
399
+
400
+ - **Full bundle**: ~15KB gzipped
401
+ - **Core component only**: ~10KB gzipped
402
+ - **Zero runtime dependencies**
403
+
404
+ ## Customization
405
+
406
+ ### CSS Variables
407
+
408
+ Override these CSS custom properties to customize the appearance:
409
+
410
+ ```css
411
+ :root {
412
+ --rsiv-overlay-bg: rgba(0, 0, 0, 0.92);
413
+ --rsiv-control-bg: rgba(255, 255, 255, 0.12);
414
+ --rsiv-control-bg-hover: rgba(255, 255, 255, 0.22);
415
+ --rsiv-control-color: #ffffff;
416
+ --rsiv-control-size: 44px;
417
+ --rsiv-control-radius: 8px;
418
+ --rsiv-counter-bg: rgba(0, 0, 0, 0.6);
419
+ --rsiv-counter-color: #ffffff;
420
+ --rsiv-animation-duration: 200ms;
421
+ --rsiv-animation-easing: cubic-bezier(0.4, 0, 0.2, 1);
422
+ --rsiv-focus-ring: 0 0 0 2px rgba(66, 153, 225, 0.6);
423
+ }
424
+ ```
425
+
426
+ ### Example: Dark Theme
427
+
428
+ ```css
429
+ .my-viewer {
430
+ --rsiv-overlay-bg: rgba(10, 10, 10, 0.98);
431
+ --rsiv-control-bg: rgba(255, 255, 255, 0.08);
432
+ --rsiv-control-bg-hover: rgba(255, 255, 255, 0.16);
433
+ }
434
+ ```
435
+
436
+ ### Example: Light Theme
437
+
438
+ ```css
439
+ .my-light-viewer {
440
+ --rsiv-overlay-bg: rgba(255, 255, 255, 0.95);
441
+ --rsiv-control-bg: rgba(0, 0, 0, 0.08);
442
+ --rsiv-control-bg-hover: rgba(0, 0, 0, 0.16);
443
+ --rsiv-control-color: #1a1a1a;
444
+ --rsiv-counter-bg: rgba(0, 0, 0, 0.6);
445
+ }
446
+ ```
447
+
448
+ ## Browser Support
449
+
450
+ - Chrome 80+
451
+ - Firefox 75+
452
+ - Safari 13.1+
453
+ - Edge 80+
454
+
455
+ Touch gestures require a device with touch support.
456
+
457
+ ## TypeScript
458
+
459
+ Full TypeScript support with exported types:
460
+
461
+ ```typescript
462
+ import type {
463
+ ImageViewerProps,
464
+ ImageSource,
465
+ ImageInput,
466
+ UseImageViewerReturn,
467
+ UseImageViewerOptions,
468
+ ControlsRenderProps,
469
+ NavigationRenderProps,
470
+ } from 'react-smart-image-viewer';
471
+ ```
472
+
473
+ ## Contributing
474
+
475
+ Contributions are welcome! Please read our contributing guidelines before submitting a PR.
476
+
477
+ ## License
478
+
479
+ MIT © [Your Name]
480
+
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),e=require("react");function Q(t){return typeof t=="string"?{src:t,alt:""}:t}function ee(t){return(Array.isArray(t)?t:[t]).map(Q)}function P(t,r,o){return Math.min(Math.max(t,r),o)}function U(t){if(t.length<2)return 0;const r=t[0].clientX-t[1].clientX,o=t[0].clientY-t[1].clientY;return Math.sqrt(r*r+o*o)}function H(t){var r,o;return t.length<2?{x:((r=t[0])==null?void 0:r.clientX)??0,y:((o=t[0])==null?void 0:o.clientY)??0}:{x:(t[0].clientX+t[1].clientX)/2,y:(t[0].clientY+t[1].clientY)/2}}function Ce(){if(typeof document>"u")return()=>{};const t=document.body.style.overflow,r=document.body.style.paddingRight,o=window.innerWidth-document.documentElement.clientWidth;return document.body.style.overflow="hidden",o>0&&(document.body.style.paddingRight=`${o}px`),()=>{document.body.style.overflow=t,document.body.style.paddingRight=r}}function K(t){let r=null,o=null;return(...f)=>{o=f,r===null&&(r=requestAnimationFrame(()=>{o&&t(...o),r=null}))}}function Le(){return typeof window>"u"?!1:"ontouchstart"in window||navigator.maxTouchPoints>0}let De=0;function _(t="rsiv"){return`${t}-${++De}`}function te(){return typeof window>"u"}const Se=["button:not([disabled])","[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(",");function Re(t){const r=e.useRef(null),o=e.useRef(null),f=e.useCallback(()=>r.current?Array.from(r.current.querySelectorAll(Se)):[],[]),p=e.useCallback(n=>{if(n.key!=="Tab")return;const b=f();if(b.length===0)return;const k=b[0],x=b[b.length-1];n.shiftKey?document.activeElement===k&&(n.preventDefault(),x.focus()):document.activeElement===x&&(n.preventDefault(),k.focus())},[f]);return e.useEffect(()=>{if(!t)return;o.current=document.activeElement;const n=f();return n.length>0&&requestAnimationFrame(()=>{n[0].focus()}),document.addEventListener("keydown",p),()=>{document.removeEventListener("keydown",p),o.current instanceof HTMLElement&&o.current.focus()}},[t,f,p]),r}const J={scale:1,translateX:0,translateY:0};function Ye(t){const{minZoom:r,maxZoom:o,zoomStep:f,onZoomChange:p}=t,[n,b]=e.useState(J),[k,x]=e.useState(!1),y=e.useRef(null),S=e.useRef(null),c=e.useRef({isGesturing:!1,startDistance:0,startScale:1,lastPosition:null,pinchCenter:null});e.useEffect(()=>{p==null||p(n.scale)},[n.scale,p]);const m=e.useCallback(l=>{b(u=>{const h={...u,...l};return h.scale=P(h.scale,r,o),h})},[r,o]),N=e.useCallback(()=>{m({scale:n.scale+f})},[n.scale,f,m]),C=e.useCallback(()=>{m({scale:n.scale-f})},[n.scale,f,m]),L=e.useCallback(()=>{b(J)},[]),X=e.useCallback(l=>{const u=P(l,r,o);b({scale:u,translateX:0,translateY:0})},[r,o]),R=e.useCallback(l=>{l.preventDefault();const u=l.deltaY>0?-f*.5:f*.5,h=P(n.scale+u,r,o);if(y.current){const i=y.current.getBoundingClientRect(),v=l.clientX-i.left-i.width/2,g=l.clientY-i.top-i.height/2,a=h/n.scale,d=n.translateX*a-v*(a-1),Y=n.translateY*a-g*(a-1);b({scale:h,translateX:d,translateY:Y})}else m({scale:h})},[n,f,r,o,m]),z=e.useCallback(l=>{if(l.preventDefault(),n.scale>1)L();else{const u=Math.min(2,o);if(y.current){const h=y.current.getBoundingClientRect(),i=l.clientX-h.left-h.width/2,v=l.clientY-h.top-h.height/2;b({scale:u,translateX:-i*(u-1),translateY:-v*(u-1)})}else m({scale:u})}},[n.scale,o,L,m]),j=e.useCallback(l=>{if(l.button!==0||n.scale<=1)return;l.preventDefault(),x(!0),c.current.lastPosition={x:l.clientX,y:l.clientY};const u=K(i=>{if(!c.current.lastPosition)return;const v=i.clientX-c.current.lastPosition.x,g=i.clientY-c.current.lastPosition.y;b(a=>({...a,translateX:a.translateX+v,translateY:a.translateY+g})),c.current.lastPosition={x:i.clientX,y:i.clientY}}),h=()=>{x(!1),c.current.lastPosition=null,document.removeEventListener("mousemove",u),document.removeEventListener("mouseup",h)};document.addEventListener("mousemove",u),document.addEventListener("mouseup",h)},[n.scale]),M=e.useCallback(l=>{l.touches.length===2?(l.preventDefault(),c.current={isGesturing:!0,startDistance:U(l.touches),startScale:n.scale,lastPosition:null,pinchCenter:H(l.touches)}):l.touches.length===1&&n.scale>1&&(c.current.lastPosition={x:l.touches[0].clientX,y:l.touches[0].clientY},x(!0));const u=K(i=>{if(i.touches.length===2&&c.current.isGesturing){i.preventDefault();const g=U(i.touches)/c.current.startDistance*c.current.startScale,a=P(g,r,o),d=H(i.touches);if(c.current.pinchCenter&&y.current){const Y=y.current.getBoundingClientRect(),w=c.current.pinchCenter.x-Y.left-Y.width/2,$=c.current.pinchCenter.y-Y.top-Y.height/2,I=a/n.scale;b({scale:a,translateX:n.translateX*I-w*(I-1)+(d.x-c.current.pinchCenter.x),translateY:n.translateY*I-$*(I-1)+(d.y-c.current.pinchCenter.y)})}else m({scale:a})}else if(i.touches.length===1&&c.current.lastPosition){const v=i.touches[0],g=v.clientX-c.current.lastPosition.x,a=v.clientY-c.current.lastPosition.y;b(d=>({...d,translateX:d.translateX+g,translateY:d.translateY+a})),c.current.lastPosition={x:v.clientX,y:v.clientY}}}),h=i=>{i.touches.length===0?(c.current={isGesturing:!1,startDistance:0,startScale:1,lastPosition:null,pinchCenter:null},x(!1),document.removeEventListener("touchmove",u),document.removeEventListener("touchend",h)):i.touches.length===1&&(c.current.isGesturing=!1,c.current.lastPosition={x:i.touches[0].clientX,y:i.touches[0].clientY})};document.addEventListener("touchmove",u,{passive:!1}),document.addEventListener("touchend",h)},[n,r,o,m]);return{transform:n,isDragging:k,zoomIn:N,zoomOut:C,resetZoom:L,setZoom:X,handleWheel:R,handleMouseDown:j,handleTouchStart:M,handleDoubleClick:z,containerRef:y,imageRef:S}}const ne=t=>s.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",...t,children:[s.jsx("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),s.jsx("line",{x1:"6",y1:"6",x2:"18",y2:"18"})]}),se=t=>s.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",...t,children:[s.jsx("circle",{cx:"11",cy:"11",r:"8"}),s.jsx("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"}),s.jsx("line",{x1:"11",y1:"8",x2:"11",y2:"14"}),s.jsx("line",{x1:"8",y1:"11",x2:"14",y2:"11"})]}),re=t=>s.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",...t,children:[s.jsx("circle",{cx:"11",cy:"11",r:"8"}),s.jsx("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"}),s.jsx("line",{x1:"8",y1:"11",x2:"14",y2:"11"})]}),oe=t=>s.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",...t,children:[s.jsx("polyline",{points:"1 4 1 10 7 10"}),s.jsx("path",{d:"M3.51 15a9 9 0 1 0 2.13-9.36L1 10"})]}),le=t=>s.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",...t,children:s.jsx("polyline",{points:"15 18 9 12 15 6"})}),ae=t=>s.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",...t,children:s.jsx("polyline",{points:"9 18 15 12 9 6"})}),ce=({images:t,initialIndex:r=0,isOpen:o,defaultOpen:f=!1,onClose:p,onIndexChange:n,zoomStep:b=.5,minZoom:k=.5,maxZoom:x=4,showControls:y=!0,showNavigation:S=!0,showCounter:c=!0,closeOnOverlayClick:m=!0,closeOnEscape:N=!0,enableKeyboardNavigation:C=!0,loop:L=!1,className:X="",imageClassName:R="",animationDuration:z=200,ariaLabel:j="Image viewer",renderControls:M,renderNavigation:l})=>{const u=o!==void 0,[h,i]=e.useState(f),v=u?o:h,g=e.useMemo(()=>ee(t),[t]),a=g.length>1,[d,Y]=e.useState(r),w=g[d],[$,I]=e.useState(!0),ie=e.useRef(_("rsiv-viewer")),W=e.useRef(_("rsiv-title")),{transform:T,isDragging:ue,zoomIn:B,zoomOut:G,resetZoom:E,handleWheel:de,handleMouseDown:fe,handleTouchStart:me,handleDoubleClick:he,containerRef:ge,imageRef:ve}=Ye({minZoom:k,maxZoom:x,zoomStep:b}),pe=Re(v);e.useEffect(()=>{te()||document.documentElement.style.setProperty("--rsiv-animation-duration",`${z}ms`)},[z]),e.useEffect(()=>{if(v)return Ce()},[v]),e.useEffect(()=>{E(),I(!0)},[d,E]),e.useEffect(()=>{r!==d&&r>=0&&r<g.length&&Y(r)},[r,g.length]);const A=e.useCallback(()=>{u||i(!1),E(),p==null||p()},[u,p,E]),O=L||d<g.length-1,q=L||d>0,F=e.useCallback(()=>{if(!O)return;const D=d<g.length-1?d+1:0;Y(D),n==null||n(D)},[d,g.length,O,n]),V=e.useCallback(()=>{if(!q)return;const D=d>0?d-1:g.length-1;Y(D),n==null||n(D)},[d,g.length,q,n]);e.useEffect(()=>{if(!v)return;const D=Z=>{switch(Z.key){case"Escape":N&&(Z.preventDefault(),A());break;case"ArrowLeft":C&&a&&(Z.preventDefault(),V());break;case"ArrowRight":C&&a&&(Z.preventDefault(),F());break;case"+":case"=":Z.preventDefault(),B();break;case"-":Z.preventDefault(),G();break;case"0":Z.preventDefault(),E();break}};return document.addEventListener("keydown",D),()=>document.removeEventListener("keydown",D)},[v,N,C,a,A,F,V,B,G,E]);const be=e.useCallback(D=>{m&&D.target===D.currentTarget&&A()},[m,A]),xe=e.useCallback(()=>{I(!1)},[]),ye=e.useCallback(()=>{I(!1)},[]),we={transform:`translate(${T.translateX}px, ${T.translateY}px) scale(${T.scale})`,transition:ue?"none":void 0};if(!v)return null;const ke={zoomIn:B,zoomOut:G,resetZoom:E,currentZoom:T.scale,minZoom:k,maxZoom:x,close:A},je={goToPrevious:V,goToNext:F,currentIndex:d,totalImages:g.length,canGoPrevious:q,canGoNext:O};return s.jsxs("div",{ref:pe,className:`rsiv-overlay ${X}`,"data-open":v,role:"dialog","aria-modal":"true","aria-label":j,"aria-labelledby":w!=null&&w.title?W.current:void 0,id:ie.current,onClick:be,children:[s.jsx("button",{className:"rsiv-button rsiv-close",onClick:A,"aria-label":"Close image viewer",type:"button",children:s.jsx(ne,{"aria-hidden":!0})}),(w==null?void 0:w.title)&&s.jsx("div",{className:"rsiv-title",id:W.current,children:w.title}),s.jsxs("div",{ref:ge,className:"rsiv-container",onWheel:de,onTouchStart:me,children:[$&&s.jsx("div",{className:"rsiv-loader","aria-label":"Loading image"}),s.jsx("div",{className:"rsiv-image-wrapper",style:we,children:w&&s.jsx("img",{ref:ve,src:w.src,alt:w.alt||"",className:`rsiv-image ${R}`,"data-loading":$,"data-zoomed":T.scale>1,onLoad:xe,onError:ye,onMouseDown:fe,onDoubleClick:he,draggable:!1})})]}),a&&S&&(l?l(je):s.jsxs(s.Fragment,{children:[s.jsx("button",{className:"rsiv-button rsiv-nav rsiv-nav-prev",onClick:V,disabled:!q,"aria-label":"Previous image",type:"button",children:s.jsx(le,{"aria-hidden":!0})}),s.jsx("button",{className:"rsiv-button rsiv-nav rsiv-nav-next",onClick:F,disabled:!O,"aria-label":"Next image",type:"button",children:s.jsx(ae,{"aria-hidden":!0})})]})),a&&c&&s.jsxs("div",{className:"rsiv-counter","aria-live":"polite",children:[d+1," / ",g.length]}),y&&(M?M(ke):s.jsxs("div",{className:"rsiv-zoom-controls",children:[s.jsx("button",{className:"rsiv-button",onClick:G,disabled:T.scale<=k,"aria-label":"Zoom out",type:"button",children:s.jsx(re,{"aria-hidden":!0})}),s.jsx("button",{className:"rsiv-button",onClick:E,disabled:T.scale===1,"aria-label":"Reset zoom",type:"button",children:s.jsx(oe,{"aria-hidden":!0})}),s.jsx("button",{className:"rsiv-button",onClick:B,disabled:T.scale>=x,"aria-label":"Zoom in",type:"button",children:s.jsx(se,{"aria-hidden":!0})})]})),s.jsxs("div",{className:"rsiv-sr-only","aria-live":"polite","aria-atomic":"true",children:[a&&`Image ${d+1} of ${g.length}`,(w==null?void 0:w.alt)&&`. ${w.alt}`]})]})};ce.displayName="ImageViewer";function Xe(t={}){const{defaultOpen:r=!1,defaultIndex:o=0,totalImages:f=1,zoomStep:p=.5,minZoom:n=.5,maxZoom:b=4,loop:k=!1,onOpenChange:x,onIndexChange:y}=t,[S,c]=e.useState(r),[m,N]=e.useState(o),[C,L]=e.useState(1),X=e.useCallback(a=>{typeof a=="number"&&(N(P(a,0,f-1)),y==null||y(a)),c(!0),x==null||x(!0)},[f,x,y]),R=e.useCallback(()=>{c(!1),L(1),x==null||x(!1)},[x]),z=e.useCallback(()=>{S?R():X()},[S,X,R]),j=e.useCallback(a=>{const d=P(a,0,f-1);N(d),L(1),y==null||y(d)},[f,y]),M=e.useCallback(()=>{m<f-1?j(m+1):k&&j(0)},[m,f,k,j]),l=e.useCallback(()=>{m>0?j(m-1):k&&j(f-1)},[m,f,k,j]),u=e.useCallback(a=>{L(P(a,n,b))},[n,b]),h=e.useCallback(()=>{u(C+p)},[C,p,u]),i=e.useCallback(()=>{u(C-p)},[C,p,u]),v=e.useCallback(()=>{L(1)},[]),g=e.useCallback(()=>({isOpen:S,onClose:R,initialIndex:m,onIndexChange:j,zoomStep:p,minZoom:n,maxZoom:b,loop:k}),[S,R,m,j,p,n,b,k]);return e.useMemo(()=>({isOpen:S,open:X,close:R,toggle:z,currentIndex:m,setCurrentIndex:j,goToNext:M,goToPrevious:l,zoom:C,zoomIn:h,zoomOut:i,resetZoom:v,setZoom:u,getViewerProps:g}),[S,X,R,z,m,j,M,l,C,h,i,v,u,g])}exports.ChevronLeftIcon=le;exports.ChevronRightIcon=ae;exports.CloseIcon=ne;exports.ImageViewer=ce;exports.ResetIcon=oe;exports.ZoomInIcon=se;exports.ZoomOutIcon=re;exports.clamp=P;exports.isSSR=te;exports.isTouchDevice=Le;exports.normalizeImage=Q;exports.normalizeImages=ee;exports.useImageViewer=Xe;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/utils/index.ts","../src/hooks/useFocusTrap.ts","../src/hooks/useZoomPan.ts","../src/components/Icons.tsx","../src/components/ImageViewer.tsx","../src/hooks/useImageViewer.ts"],"sourcesContent":["import type { ImageInput, NormalizedImage, Position, TransformState } from '../types';\n\n/**\n * Normalizes image input to consistent ImageSource format\n */\nexport function normalizeImage(image: ImageInput): NormalizedImage {\n if (typeof image === 'string') {\n return { src: image, alt: '' };\n }\n return image;\n}\n\n/**\n * Normalizes array of images\n */\nexport function normalizeImages(images: ImageInput | ImageInput[]): NormalizedImage[] {\n const imageArray = Array.isArray(images) ? images : [images];\n return imageArray.map(normalizeImage);\n}\n\n/**\n * Clamps a value between min and max\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Touch-like interface for compatibility\n */\ninterface TouchLike {\n clientX: number;\n clientY: number;\n}\n\ninterface TouchListLike {\n length: number;\n [index: number]: TouchLike;\n}\n\n/**\n * Calculates distance between two touch points\n */\nexport function getTouchDistance(touches: TouchListLike): number {\n if (touches.length < 2) return 0;\n const dx = touches[0].clientX - touches[1].clientX;\n const dy = touches[0].clientY - touches[1].clientY;\n return Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Gets center point between two touches\n */\nexport function getTouchCenter(touches: TouchListLike): Position {\n if (touches.length < 2) {\n return { x: touches[0]?.clientX ?? 0, y: touches[0]?.clientY ?? 0 };\n }\n return {\n x: (touches[0].clientX + touches[1].clientX) / 2,\n y: (touches[0].clientY + touches[1].clientY) / 2,\n };\n}\n\n/**\n * Constrains transform to prevent image from going too far off-screen\n */\nexport function constrainTransform(\n transform: TransformState,\n containerWidth: number,\n containerHeight: number,\n imageWidth: number,\n imageHeight: number\n): TransformState {\n const scaledWidth = imageWidth * transform.scale;\n const scaledHeight = imageHeight * transform.scale;\n \n // If image is smaller than container, center it\n if (scaledWidth <= containerWidth) {\n transform.translateX = 0;\n } else {\n // Allow panning but keep image edges visible\n const maxTranslateX = (scaledWidth - containerWidth) / 2;\n transform.translateX = clamp(transform.translateX, -maxTranslateX, maxTranslateX);\n }\n \n if (scaledHeight <= containerHeight) {\n transform.translateY = 0;\n } else {\n const maxTranslateY = (scaledHeight - containerHeight) / 2;\n transform.translateY = clamp(transform.translateY, -maxTranslateY, maxTranslateY);\n }\n \n return transform;\n}\n\n/**\n * Prevents body scroll\n */\nexport function preventBodyScroll(): () => void {\n // Check if we're in a browser environment\n if (typeof document === 'undefined') {\n return () => {};\n }\n \n const originalStyle = document.body.style.overflow;\n const originalPaddingRight = document.body.style.paddingRight;\n \n // Get scrollbar width\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;\n \n document.body.style.overflow = 'hidden';\n if (scrollbarWidth > 0) {\n document.body.style.paddingRight = `${scrollbarWidth}px`;\n }\n \n return () => {\n document.body.style.overflow = originalStyle;\n document.body.style.paddingRight = originalPaddingRight;\n };\n}\n\n/**\n * Creates a throttled function using requestAnimationFrame\n */\nexport function rafThrottle<T extends (...args: never[]) => void>(\n fn: T\n): (...args: Parameters<T>) => void {\n let rafId: number | null = null;\n let lastArgs: Parameters<T> | null = null;\n \n return (...args: Parameters<T>) => {\n lastArgs = args;\n \n if (rafId === null) {\n rafId = requestAnimationFrame(() => {\n if (lastArgs) {\n fn(...lastArgs);\n }\n rafId = null;\n });\n }\n };\n}\n\n/**\n * Detects if device supports touch\n */\nexport function isTouchDevice(): boolean {\n if (typeof window === 'undefined') return false;\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n}\n\n/**\n * Generates unique ID for accessibility\n */\nlet idCounter = 0;\nexport function generateId(prefix = 'rsiv'): string {\n return `${prefix}-${++idCounter}`;\n}\n\n/**\n * Check if we're in SSR environment\n */\nexport function isSSR(): boolean {\n return typeof window === 'undefined';\n}\n\n","import { useEffect, useRef, useCallback } from 'react';\n\nconst FOCUSABLE_ELEMENTS = [\n 'button:not([disabled])',\n '[href]',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/**\n * Hook to trap focus within a container element\n * Essential for modal accessibility\n */\nexport function useFocusTrap(isActive: boolean) {\n const containerRef = useRef<HTMLDivElement>(null);\n const previousActiveElement = useRef<Element | null>(null);\n\n const getFocusableElements = useCallback(() => {\n if (!containerRef.current) return [];\n return Array.from(\n containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENTS)\n );\n }, []);\n\n const handleKeyDown = useCallback((event: KeyboardEvent) => {\n if (event.key !== 'Tab') return;\n\n const focusableElements = getFocusableElements();\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n // Shift + Tab (backwards)\n if (event.shiftKey) {\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement.focus();\n }\n } else {\n // Tab (forwards)\n if (document.activeElement === lastElement) {\n event.preventDefault();\n firstElement.focus();\n }\n }\n }, [getFocusableElements]);\n\n useEffect(() => {\n if (!isActive) return;\n\n // Store the currently focused element\n previousActiveElement.current = document.activeElement;\n\n // Focus the first focusable element in the trap\n const focusableElements = getFocusableElements();\n if (focusableElements.length > 0) {\n // Small delay to ensure the modal is rendered\n requestAnimationFrame(() => {\n focusableElements[0].focus();\n });\n }\n\n // Add keyboard listener\n document.addEventListener('keydown', handleKeyDown);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n\n // Restore focus to the previous element\n if (previousActiveElement.current instanceof HTMLElement) {\n previousActiveElement.current.focus();\n }\n };\n }, [isActive, getFocusableElements, handleKeyDown]);\n\n return containerRef;\n}\n\n","import { useState, useCallback, useRef, useEffect } from 'react';\nimport type { TransformState, GestureState } from '../types';\nimport { clamp, getTouchDistance, getTouchCenter, rafThrottle } from '../utils';\n\ninterface UseZoomPanOptions {\n minZoom: number;\n maxZoom: number;\n zoomStep: number;\n onZoomChange?: (zoom: number) => void;\n}\n\ninterface UseZoomPanReturn {\n transform: TransformState;\n isDragging: boolean;\n zoomIn: () => void;\n zoomOut: () => void;\n resetZoom: () => void;\n setZoom: (zoom: number) => void;\n handleWheel: (e: React.WheelEvent) => void;\n handleMouseDown: (e: React.MouseEvent) => void;\n handleTouchStart: (e: React.TouchEvent) => void;\n handleDoubleClick: (e: React.MouseEvent) => void;\n containerRef: React.RefObject<HTMLDivElement>;\n imageRef: React.RefObject<HTMLImageElement>;\n}\n\nconst initialTransform: TransformState = {\n scale: 1,\n translateX: 0,\n translateY: 0,\n};\n\n/**\n * Hook to handle zoom and pan interactions\n */\nexport function useZoomPan(options: UseZoomPanOptions): UseZoomPanReturn {\n const { minZoom, maxZoom, zoomStep, onZoomChange } = options;\n\n const [transform, setTransform] = useState<TransformState>(initialTransform);\n const [isDragging, setIsDragging] = useState(false);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const imageRef = useRef<HTMLImageElement>(null);\n const gestureRef = useRef<GestureState>({\n isGesturing: false,\n startDistance: 0,\n startScale: 1,\n lastPosition: null,\n pinchCenter: null,\n });\n\n // Notify parent of zoom changes\n useEffect(() => {\n onZoomChange?.(transform.scale);\n }, [transform.scale, onZoomChange]);\n\n const updateTransform = useCallback((updates: Partial<TransformState>) => {\n setTransform((prev) => {\n const newTransform = { ...prev, ...updates };\n newTransform.scale = clamp(newTransform.scale, minZoom, maxZoom);\n return newTransform;\n });\n }, [minZoom, maxZoom]);\n\n const zoomIn = useCallback(() => {\n updateTransform({ scale: transform.scale + zoomStep });\n }, [transform.scale, zoomStep, updateTransform]);\n\n const zoomOut = useCallback(() => {\n updateTransform({ scale: transform.scale - zoomStep });\n }, [transform.scale, zoomStep, updateTransform]);\n\n const resetZoom = useCallback(() => {\n setTransform(initialTransform);\n }, []);\n\n const setZoom = useCallback((zoom: number) => {\n const clampedZoom = clamp(zoom, minZoom, maxZoom);\n // Reset position when setting zoom directly\n setTransform({\n scale: clampedZoom,\n translateX: 0,\n translateY: 0,\n });\n }, [minZoom, maxZoom]);\n\n // Mouse wheel zoom\n const handleWheel = useCallback((e: React.WheelEvent) => {\n e.preventDefault();\n const delta = e.deltaY > 0 ? -zoomStep * 0.5 : zoomStep * 0.5;\n const newScale = clamp(transform.scale + delta, minZoom, maxZoom);\n\n // Zoom towards cursor position\n if (containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left - rect.width / 2;\n const y = e.clientY - rect.top - rect.height / 2;\n\n const scaleDiff = newScale / transform.scale;\n const newTranslateX = transform.translateX * scaleDiff - x * (scaleDiff - 1);\n const newTranslateY = transform.translateY * scaleDiff - y * (scaleDiff - 1);\n\n setTransform({\n scale: newScale,\n translateX: newTranslateX,\n translateY: newTranslateY,\n });\n } else {\n updateTransform({ scale: newScale });\n }\n }, [transform, zoomStep, minZoom, maxZoom, updateTransform]);\n\n // Double click to zoom\n const handleDoubleClick = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n \n if (transform.scale > 1) {\n // Reset zoom\n resetZoom();\n } else {\n // Zoom in to 2x at click position\n const targetScale = Math.min(2, maxZoom);\n \n if (containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left - rect.width / 2;\n const y = e.clientY - rect.top - rect.height / 2;\n \n setTransform({\n scale: targetScale,\n translateX: -x * (targetScale - 1),\n translateY: -y * (targetScale - 1),\n });\n } else {\n updateTransform({ scale: targetScale });\n }\n }\n }, [transform.scale, maxZoom, resetZoom, updateTransform]);\n\n // Mouse drag handlers\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\n if (e.button !== 0) return; // Only left click\n if (transform.scale <= 1) return; // Only drag when zoomed\n\n e.preventDefault();\n setIsDragging(true);\n gestureRef.current.lastPosition = { x: e.clientX, y: e.clientY };\n\n const handleMouseMove = rafThrottle((moveEvent: MouseEvent) => {\n if (!gestureRef.current.lastPosition) return;\n\n const deltaX = moveEvent.clientX - gestureRef.current.lastPosition.x;\n const deltaY = moveEvent.clientY - gestureRef.current.lastPosition.y;\n\n setTransform((prev) => ({\n ...prev,\n translateX: prev.translateX + deltaX,\n translateY: prev.translateY + deltaY,\n }));\n\n gestureRef.current.lastPosition = { x: moveEvent.clientX, y: moveEvent.clientY };\n });\n\n const handleMouseUp = () => {\n setIsDragging(false);\n gestureRef.current.lastPosition = null;\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n }, [transform.scale]);\n\n // Touch handlers for pinch-to-zoom and drag\n const handleTouchStart = useCallback((e: React.TouchEvent) => {\n if (e.touches.length === 2) {\n // Pinch gesture start\n e.preventDefault();\n gestureRef.current = {\n isGesturing: true,\n startDistance: getTouchDistance(e.touches),\n startScale: transform.scale,\n lastPosition: null,\n pinchCenter: getTouchCenter(e.touches),\n };\n } else if (e.touches.length === 1 && transform.scale > 1) {\n // Single touch drag when zoomed\n gestureRef.current.lastPosition = {\n x: e.touches[0].clientX,\n y: e.touches[0].clientY,\n };\n setIsDragging(true);\n }\n\n const handleTouchMove = rafThrottle((moveEvent: TouchEvent) => {\n if (moveEvent.touches.length === 2 && gestureRef.current.isGesturing) {\n // Pinch zoom\n moveEvent.preventDefault();\n const currentDistance = getTouchDistance(moveEvent.touches);\n const scale = (currentDistance / gestureRef.current.startDistance) * gestureRef.current.startScale;\n const clampedScale = clamp(scale, minZoom, maxZoom);\n\n // Zoom towards pinch center\n const newCenter = getTouchCenter(moveEvent.touches);\n if (gestureRef.current.pinchCenter && containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n const centerX = gestureRef.current.pinchCenter.x - rect.left - rect.width / 2;\n const centerY = gestureRef.current.pinchCenter.y - rect.top - rect.height / 2;\n\n const scaleDiff = clampedScale / transform.scale;\n \n setTransform({\n scale: clampedScale,\n translateX: transform.translateX * scaleDiff - centerX * (scaleDiff - 1) + (newCenter.x - gestureRef.current.pinchCenter.x),\n translateY: transform.translateY * scaleDiff - centerY * (scaleDiff - 1) + (newCenter.y - gestureRef.current.pinchCenter.y),\n });\n } else {\n updateTransform({ scale: clampedScale });\n }\n } else if (moveEvent.touches.length === 1 && gestureRef.current.lastPosition) {\n // Single touch drag\n const touch = moveEvent.touches[0];\n const deltaX = touch.clientX - gestureRef.current.lastPosition.x;\n const deltaY = touch.clientY - gestureRef.current.lastPosition.y;\n\n setTransform((prev) => ({\n ...prev,\n translateX: prev.translateX + deltaX,\n translateY: prev.translateY + deltaY,\n }));\n\n gestureRef.current.lastPosition = { x: touch.clientX, y: touch.clientY };\n }\n });\n\n const handleTouchEnd = (endEvent: TouchEvent) => {\n if (endEvent.touches.length === 0) {\n gestureRef.current = {\n isGesturing: false,\n startDistance: 0,\n startScale: 1,\n lastPosition: null,\n pinchCenter: null,\n };\n setIsDragging(false);\n document.removeEventListener('touchmove', handleTouchMove);\n document.removeEventListener('touchend', handleTouchEnd);\n } else if (endEvent.touches.length === 1) {\n // Transition from pinch to drag\n gestureRef.current.isGesturing = false;\n gestureRef.current.lastPosition = {\n x: endEvent.touches[0].clientX,\n y: endEvent.touches[0].clientY,\n };\n }\n };\n\n document.addEventListener('touchmove', handleTouchMove, { passive: false });\n document.addEventListener('touchend', handleTouchEnd);\n }, [transform, minZoom, maxZoom, updateTransform]);\n\n return {\n transform,\n isDragging,\n zoomIn,\n zoomOut,\n resetZoom,\n setZoom,\n handleWheel,\n handleMouseDown,\n handleTouchStart,\n handleDoubleClick,\n containerRef,\n imageRef,\n };\n}\n\n","import React from 'react';\n\ninterface IconProps {\n className?: string;\n 'aria-hidden'?: boolean;\n}\n\n/**\n * SVG Icons for the ImageViewer controls\n * Using inline SVGs to avoid external dependencies\n */\n\nexport const CloseIcon: React.FC<IconProps> = (props) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n);\n\nexport const ZoomInIcon: React.FC<IconProps> = (props) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n <line x1=\"11\" y1=\"8\" x2=\"11\" y2=\"14\" />\n <line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\" />\n </svg>\n);\n\nexport const ZoomOutIcon: React.FC<IconProps> = (props) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n <line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\" />\n </svg>\n);\n\nexport const ResetIcon: React.FC<IconProps> = (props) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"1 4 1 10 7 10\" />\n <path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\" />\n </svg>\n);\n\nexport const ChevronLeftIcon: React.FC<IconProps> = (props) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n);\n\nexport const ChevronRightIcon: React.FC<IconProps> = (props) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n);\n\n","import React, {\n useState,\n useEffect,\n useCallback,\n useMemo,\n useRef,\n} from 'react';\nimport type { ImageViewerProps, NormalizedImage } from '../types';\nimport { normalizeImages, preventBodyScroll, generateId, isSSR } from '../utils';\nimport { useFocusTrap } from '../hooks/useFocusTrap';\nimport { useZoomPan } from '../hooks/useZoomPan';\nimport {\n CloseIcon,\n ZoomInIcon,\n ZoomOutIcon,\n ResetIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n} from './Icons';\nimport '../styles/index.css';\n\n/**\n * ImageViewer - A high-performance image viewer with zoom, pan, and gallery support\n *\n * @example\n * ```tsx\n * // Single image\n * <ImageViewer images=\"https://example.com/image.jpg\" isOpen={isOpen} onClose={() => setIsOpen(false)} />\n *\n * // Gallery\n * <ImageViewer images={['image1.jpg', 'image2.jpg']} isOpen={isOpen} onClose={() => setIsOpen(false)} />\n * ```\n */\nexport const ImageViewer: React.FC<ImageViewerProps> = ({\n images,\n initialIndex = 0,\n isOpen: controlledIsOpen,\n defaultOpen = false,\n onClose,\n onIndexChange,\n zoomStep = 0.5,\n minZoom = 0.5,\n maxZoom = 4,\n showControls = true,\n showNavigation = true,\n showCounter = true,\n closeOnOverlayClick = true,\n closeOnEscape = true,\n enableKeyboardNavigation = true,\n loop = false,\n className = '',\n imageClassName = '',\n animationDuration = 200,\n ariaLabel = 'Image viewer',\n renderControls,\n renderNavigation,\n}) => {\n // Determine controlled vs uncontrolled mode\n const isControlled = controlledIsOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultOpen);\n const isOpen = isControlled ? controlledIsOpen : internalIsOpen;\n\n // Normalize images\n const normalizedImages = useMemo(() => normalizeImages(images), [images]);\n const isGallery = normalizedImages.length > 1;\n\n // Image index state\n const [currentIndex, setCurrentIndex] = useState(initialIndex);\n const currentImage: NormalizedImage | undefined = normalizedImages[currentIndex];\n\n // Loading state\n const [isLoading, setIsLoading] = useState(true);\n\n // Generate unique IDs for accessibility\n const viewerId = useRef(generateId('rsiv-viewer'));\n const titleId = useRef(generateId('rsiv-title'));\n\n // Zoom and pan\n const {\n transform,\n isDragging,\n zoomIn,\n zoomOut,\n resetZoom,\n handleWheel,\n handleMouseDown,\n handleTouchStart,\n handleDoubleClick,\n containerRef,\n imageRef,\n } = useZoomPan({\n minZoom,\n maxZoom,\n zoomStep,\n });\n\n // Focus trap for accessibility\n const focusTrapRef = useFocusTrap(isOpen);\n\n // Apply CSS custom property for animation duration\n useEffect(() => {\n if (!isSSR()) {\n document.documentElement.style.setProperty(\n '--rsiv-animation-duration',\n `${animationDuration}ms`\n );\n }\n }, [animationDuration]);\n\n // Prevent body scroll when open\n useEffect(() => {\n if (isOpen) {\n return preventBodyScroll();\n }\n }, [isOpen]);\n\n // Reset zoom when image changes\n useEffect(() => {\n resetZoom();\n setIsLoading(true);\n }, [currentIndex, resetZoom]);\n\n // Sync external index changes\n useEffect(() => {\n if (initialIndex !== currentIndex && initialIndex >= 0 && initialIndex < normalizedImages.length) {\n setCurrentIndex(initialIndex);\n }\n }, [initialIndex, normalizedImages.length]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Close handler\n const handleClose = useCallback(() => {\n if (!isControlled) {\n setInternalIsOpen(false);\n }\n resetZoom();\n onClose?.();\n }, [isControlled, onClose, resetZoom]);\n\n // Navigation handlers\n const canGoNext = loop || currentIndex < normalizedImages.length - 1;\n const canGoPrevious = loop || currentIndex > 0;\n\n const goToNext = useCallback(() => {\n if (!canGoNext) return;\n\n const nextIndex = currentIndex < normalizedImages.length - 1\n ? currentIndex + 1\n : 0;\n setCurrentIndex(nextIndex);\n onIndexChange?.(nextIndex);\n }, [currentIndex, normalizedImages.length, canGoNext, onIndexChange]);\n\n const goToPrevious = useCallback(() => {\n if (!canGoPrevious) return;\n\n const prevIndex = currentIndex > 0\n ? currentIndex - 1\n : normalizedImages.length - 1;\n setCurrentIndex(prevIndex);\n onIndexChange?.(prevIndex);\n }, [currentIndex, normalizedImages.length, canGoPrevious, onIndexChange]);\n\n // Keyboard navigation\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'Escape':\n if (closeOnEscape) {\n e.preventDefault();\n handleClose();\n }\n break;\n case 'ArrowLeft':\n if (enableKeyboardNavigation && isGallery) {\n e.preventDefault();\n goToPrevious();\n }\n break;\n case 'ArrowRight':\n if (enableKeyboardNavigation && isGallery) {\n e.preventDefault();\n goToNext();\n }\n break;\n case '+':\n case '=':\n e.preventDefault();\n zoomIn();\n break;\n case '-':\n e.preventDefault();\n zoomOut();\n break;\n case '0':\n e.preventDefault();\n resetZoom();\n break;\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [\n isOpen,\n closeOnEscape,\n enableKeyboardNavigation,\n isGallery,\n handleClose,\n goToNext,\n goToPrevious,\n zoomIn,\n zoomOut,\n resetZoom,\n ]);\n\n // Overlay click handler\n const handleOverlayClick = useCallback((e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n handleClose();\n }\n }, [closeOnOverlayClick, handleClose]);\n\n // Image load handler\n const handleImageLoad = useCallback(() => {\n setIsLoading(false);\n }, []);\n\n // Image error handler\n const handleImageError = useCallback(() => {\n setIsLoading(false);\n }, []);\n\n // Transform style\n const transformStyle: React.CSSProperties = {\n transform: `translate(${transform.translateX}px, ${transform.translateY}px) scale(${transform.scale})`,\n transition: isDragging ? 'none' : undefined,\n };\n\n // Don't render anything if not open and SSR\n if (!isOpen) {\n return null;\n }\n\n // Custom controls render\n const controlsRenderProps = {\n zoomIn,\n zoomOut,\n resetZoom,\n currentZoom: transform.scale,\n minZoom,\n maxZoom,\n close: handleClose,\n };\n\n // Custom navigation render\n const navigationRenderProps = {\n goToPrevious,\n goToNext,\n currentIndex,\n totalImages: normalizedImages.length,\n canGoPrevious,\n canGoNext,\n };\n\n return (\n <div\n ref={focusTrapRef}\n className={`rsiv-overlay ${className}`}\n data-open={isOpen}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={ariaLabel}\n aria-labelledby={currentImage?.title ? titleId.current : undefined}\n id={viewerId.current}\n onClick={handleOverlayClick}\n >\n {/* Close button */}\n <button\n className=\"rsiv-button rsiv-close\"\n onClick={handleClose}\n aria-label=\"Close image viewer\"\n type=\"button\"\n >\n <CloseIcon aria-hidden />\n </button>\n\n {/* Image title */}\n {currentImage?.title && (\n <div className=\"rsiv-title\" id={titleId.current}>\n {currentImage.title}\n </div>\n )}\n\n {/* Main container */}\n <div\n ref={containerRef}\n className=\"rsiv-container\"\n onWheel={handleWheel}\n onTouchStart={handleTouchStart}\n >\n {/* Loading indicator */}\n {isLoading && <div className=\"rsiv-loader\" aria-label=\"Loading image\" />}\n\n {/* Image */}\n <div className=\"rsiv-image-wrapper\" style={transformStyle}>\n {currentImage && (\n <img\n ref={imageRef}\n src={currentImage.src}\n alt={currentImage.alt || ''}\n className={`rsiv-image ${imageClassName}`}\n data-loading={isLoading}\n data-zoomed={transform.scale > 1}\n onLoad={handleImageLoad}\n onError={handleImageError}\n onMouseDown={handleMouseDown}\n onDoubleClick={handleDoubleClick}\n draggable={false}\n />\n )}\n </div>\n </div>\n\n {/* Navigation arrows */}\n {isGallery && showNavigation && (\n renderNavigation ? (\n renderNavigation(navigationRenderProps)\n ) : (\n <>\n <button\n className=\"rsiv-button rsiv-nav rsiv-nav-prev\"\n onClick={goToPrevious}\n disabled={!canGoPrevious}\n aria-label=\"Previous image\"\n type=\"button\"\n >\n <ChevronLeftIcon aria-hidden />\n </button>\n <button\n className=\"rsiv-button rsiv-nav rsiv-nav-next\"\n onClick={goToNext}\n disabled={!canGoNext}\n aria-label=\"Next image\"\n type=\"button\"\n >\n <ChevronRightIcon aria-hidden />\n </button>\n </>\n )\n )}\n\n {/* Image counter */}\n {isGallery && showCounter && (\n <div className=\"rsiv-counter\" aria-live=\"polite\">\n {currentIndex + 1} / {normalizedImages.length}\n </div>\n )}\n\n {/* Zoom controls */}\n {showControls && (\n renderControls ? (\n renderControls(controlsRenderProps)\n ) : (\n <div className=\"rsiv-zoom-controls\">\n <button\n className=\"rsiv-button\"\n onClick={zoomOut}\n disabled={transform.scale <= minZoom}\n aria-label=\"Zoom out\"\n type=\"button\"\n >\n <ZoomOutIcon aria-hidden />\n </button>\n <button\n className=\"rsiv-button\"\n onClick={resetZoom}\n disabled={transform.scale === 1}\n aria-label=\"Reset zoom\"\n type=\"button\"\n >\n <ResetIcon aria-hidden />\n </button>\n <button\n className=\"rsiv-button\"\n onClick={zoomIn}\n disabled={transform.scale >= maxZoom}\n aria-label=\"Zoom in\"\n type=\"button\"\n >\n <ZoomInIcon aria-hidden />\n </button>\n </div>\n )\n )}\n\n {/* Screen reader announcements */}\n <div className=\"rsiv-sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {isGallery && `Image ${currentIndex + 1} of ${normalizedImages.length}`}\n {currentImage?.alt && `. ${currentImage.alt}`}\n </div>\n </div>\n );\n};\n\nImageViewer.displayName = 'ImageViewer';\n\n","import { useState, useCallback, useMemo } from 'react';\nimport type { UseImageViewerOptions, UseImageViewerReturn, ImageViewerProps } from '../types';\nimport { clamp } from '../utils';\n\n/**\n * Hook for controlling the ImageViewer programmatically\n * \n * @example\n * ```tsx\n * const viewer = useImageViewer({ totalImages: 5 });\n * \n * return (\n * <>\n * <button onClick={() => viewer.open(0)}>Open Gallery</button>\n * <ImageViewer images={images} {...viewer.getViewerProps()} />\n * </>\n * );\n * ```\n */\nexport function useImageViewer(options: UseImageViewerOptions = {}): UseImageViewerReturn {\n const {\n defaultOpen = false,\n defaultIndex = 0,\n totalImages = 1,\n zoomStep = 0.5,\n minZoom = 0.5,\n maxZoom = 4,\n loop = false,\n onOpenChange,\n onIndexChange,\n } = options;\n\n const [isOpen, setIsOpen] = useState(defaultOpen);\n const [currentIndex, setCurrentIndexState] = useState(defaultIndex);\n const [zoom, setZoomState] = useState(1);\n\n const open = useCallback((index?: number) => {\n if (typeof index === 'number') {\n setCurrentIndexState(clamp(index, 0, totalImages - 1));\n onIndexChange?.(index);\n }\n setIsOpen(true);\n onOpenChange?.(true);\n }, [totalImages, onOpenChange, onIndexChange]);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setZoomState(1);\n onOpenChange?.(false);\n }, [onOpenChange]);\n\n const toggle = useCallback(() => {\n if (isOpen) {\n close();\n } else {\n open();\n }\n }, [isOpen, open, close]);\n\n const setCurrentIndex = useCallback((index: number) => {\n const clampedIndex = clamp(index, 0, totalImages - 1);\n setCurrentIndexState(clampedIndex);\n setZoomState(1); // Reset zoom when changing images\n onIndexChange?.(clampedIndex);\n }, [totalImages, onIndexChange]);\n\n const goToNext = useCallback(() => {\n if (currentIndex < totalImages - 1) {\n setCurrentIndex(currentIndex + 1);\n } else if (loop) {\n setCurrentIndex(0);\n }\n }, [currentIndex, totalImages, loop, setCurrentIndex]);\n\n const goToPrevious = useCallback(() => {\n if (currentIndex > 0) {\n setCurrentIndex(currentIndex - 1);\n } else if (loop) {\n setCurrentIndex(totalImages - 1);\n }\n }, [currentIndex, totalImages, loop, setCurrentIndex]);\n\n const setZoom = useCallback((newZoom: number) => {\n setZoomState(clamp(newZoom, minZoom, maxZoom));\n }, [minZoom, maxZoom]);\n\n const zoomIn = useCallback(() => {\n setZoom(zoom + zoomStep);\n }, [zoom, zoomStep, setZoom]);\n\n const zoomOut = useCallback(() => {\n setZoom(zoom - zoomStep);\n }, [zoom, zoomStep, setZoom]);\n\n const resetZoom = useCallback(() => {\n setZoomState(1);\n }, []);\n\n const getViewerProps = useCallback((): Partial<ImageViewerProps> => ({\n isOpen,\n onClose: close,\n initialIndex: currentIndex,\n onIndexChange: setCurrentIndex,\n zoomStep,\n minZoom,\n maxZoom,\n loop,\n }), [isOpen, close, currentIndex, setCurrentIndex, zoomStep, minZoom, maxZoom, loop]);\n\n return useMemo(() => ({\n isOpen,\n open,\n close,\n toggle,\n currentIndex,\n setCurrentIndex,\n goToNext,\n goToPrevious,\n zoom,\n zoomIn,\n zoomOut,\n resetZoom,\n setZoom,\n getViewerProps,\n }), [\n isOpen,\n open,\n close,\n toggle,\n currentIndex,\n setCurrentIndex,\n goToNext,\n goToPrevious,\n zoom,\n zoomIn,\n zoomOut,\n resetZoom,\n setZoom,\n getViewerProps,\n ]);\n}\n\n"],"names":["normalizeImage","image","normalizeImages","images","clamp","value","min","max","getTouchDistance","touches","dx","dy","getTouchCenter","_a","_b","preventBodyScroll","originalStyle","originalPaddingRight","scrollbarWidth","rafThrottle","fn","rafId","lastArgs","args","isTouchDevice","idCounter","generateId","prefix","isSSR","FOCUSABLE_ELEMENTS","useFocusTrap","isActive","containerRef","useRef","previousActiveElement","getFocusableElements","useCallback","handleKeyDown","event","focusableElements","firstElement","lastElement","useEffect","initialTransform","useZoomPan","options","minZoom","maxZoom","zoomStep","onZoomChange","transform","setTransform","useState","isDragging","setIsDragging","imageRef","gestureRef","updateTransform","updates","prev","newTransform","zoomIn","zoomOut","resetZoom","setZoom","zoom","clampedZoom","handleWheel","e","delta","newScale","rect","x","y","scaleDiff","newTranslateX","newTranslateY","handleDoubleClick","targetScale","handleMouseDown","handleMouseMove","moveEvent","deltaX","deltaY","handleMouseUp","handleTouchStart","handleTouchMove","scale","clampedScale","newCenter","centerX","centerY","touch","handleTouchEnd","endEvent","CloseIcon","props","jsxs","jsx","ZoomInIcon","ZoomOutIcon","ResetIcon","ChevronLeftIcon","ChevronRightIcon","ImageViewer","initialIndex","controlledIsOpen","defaultOpen","onClose","onIndexChange","showControls","showNavigation","showCounter","closeOnOverlayClick","closeOnEscape","enableKeyboardNavigation","loop","className","imageClassName","animationDuration","ariaLabel","renderControls","renderNavigation","isControlled","internalIsOpen","setInternalIsOpen","isOpen","normalizedImages","useMemo","isGallery","currentIndex","setCurrentIndex","currentImage","isLoading","setIsLoading","viewerId","titleId","focusTrapRef","handleClose","canGoNext","canGoPrevious","goToNext","nextIndex","goToPrevious","prevIndex","handleOverlayClick","handleImageLoad","handleImageError","transformStyle","controlsRenderProps","navigationRenderProps","Fragment","useImageViewer","defaultIndex","totalImages","onOpenChange","setIsOpen","setCurrentIndexState","setZoomState","open","index","close","toggle","clampedIndex","newZoom","getViewerProps"],"mappings":"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"}