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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","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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"}
@@ -0,0 +1 @@
1
+ :root{--rsiv-overlay-bg: rgba(0, 0, 0, .92);--rsiv-control-bg: rgba(255, 255, 255, .12);--rsiv-control-bg-hover: rgba(255, 255, 255, .22);--rsiv-control-color: #ffffff;--rsiv-control-size: 44px;--rsiv-control-radius: 8px;--rsiv-counter-bg: rgba(0, 0, 0, .6);--rsiv-counter-color: #ffffff;--rsiv-animation-duration: .2s;--rsiv-animation-easing: cubic-bezier(.4, 0, .2, 1);--rsiv-focus-ring: 0 0 0 2px rgba(66, 153, 225, .6)}.rsiv-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:var(--rsiv-overlay-bg);opacity:0;visibility:hidden;transition:opacity var(--rsiv-animation-duration) var(--rsiv-animation-easing),visibility var(--rsiv-animation-duration) var(--rsiv-animation-easing)}.rsiv-overlay[data-open=true]{opacity:1;visibility:visible}.rsiv-overlay[data-open=false]{pointer-events:none}.rsiv-container{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none}.rsiv-image-wrapper{position:relative;display:flex;align-items:center;justify-content:center;max-width:100%;max-height:100%;will-change:transform}.rsiv-image{max-width:90vw;max-height:90vh;object-fit:contain;cursor:grab;transition:opacity var(--rsiv-animation-duration) var(--rsiv-animation-easing);-webkit-user-drag:none;-webkit-user-select:none;user-select:none}.rsiv-image[data-loading=true]{opacity:0}.rsiv-image[data-loading=false]{opacity:1}.rsiv-image[data-zoomed=true]{cursor:grabbing}.rsiv-image[data-zoomed=false]{cursor:zoom-in}.rsiv-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top-color:#fff;border-radius:50%;animation:rsiv-spin .8s linear infinite}@keyframes rsiv-spin{to{transform:translate(-50%,-50%) rotate(360deg)}}.rsiv-controls{position:absolute;top:16px;right:16px;display:flex;gap:8px;z-index:10}.rsiv-button{display:flex;align-items:center;justify-content:center;width:var(--rsiv-control-size);height:var(--rsiv-control-size);padding:0;border:none;border-radius:var(--rsiv-control-radius);background:var(--rsiv-control-bg);color:var(--rsiv-control-color);cursor:pointer;transition:background var(--rsiv-animation-duration) var(--rsiv-animation-easing),transform var(--rsiv-animation-duration) var(--rsiv-animation-easing);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.rsiv-button:hover{background:var(--rsiv-control-bg-hover)}.rsiv-button:focus-visible{outline:none;box-shadow:var(--rsiv-focus-ring)}.rsiv-button:active{transform:scale(.95)}.rsiv-button:disabled{opacity:.4;cursor:not-allowed}.rsiv-button:disabled:hover{background:var(--rsiv-control-bg)}.rsiv-button svg{width:20px;height:20px;stroke-width:2}.rsiv-close{position:absolute;top:16px;right:16px;z-index:20}.rsiv-controls+.rsiv-close{right:auto;left:16px}.rsiv-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.rsiv-nav-prev{left:16px}.rsiv-nav-next{right:16px}.rsiv-counter{position:absolute;bottom:16px;left:50%;transform:translate(-50%);padding:8px 16px;background:var(--rsiv-counter-bg);color:var(--rsiv-counter-color);border-radius:20px;font-size:14px;font-weight:500;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:10}.rsiv-zoom-controls{position:absolute;bottom:16px;right:16px;display:flex;gap:8px;z-index:10}.rsiv-title{position:absolute;top:16px;left:50%;transform:translate(-50%);max-width:60%;padding:8px 16px;background:var(--rsiv-counter-bg);color:var(--rsiv-counter-color);border-radius:8px;font-size:14px;font-weight:500;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:10}.rsiv-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width: 640px){.rsiv-image{max-width:100vw;max-height:100vh}.rsiv-controls{top:8px;right:8px;gap:4px}.rsiv-close{top:8px;right:8px}.rsiv-controls+.rsiv-close{left:8px}.rsiv-nav-prev{left:8px}.rsiv-nav-next{right:8px}.rsiv-button{width:40px;height:40px}.rsiv-button svg{width:18px;height:18px}.rsiv-zoom-controls{bottom:8px;right:8px}.rsiv-counter{bottom:8px}.rsiv-title{top:8px;max-width:50%}}@media (prefers-reduced-motion: reduce){.rsiv-overlay,.rsiv-image,.rsiv-button{transition:none}.rsiv-loader{animation:none}}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "react-smart-image-viewer",
3
+ "version": "1.0.0",
4
+ "description": "A high-performance, TypeScript-first React image viewer with zoom, pan, keyboard, and mobile gesture support",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "./styles.css": "./dist/styles.css"
20
+ },
21
+ "sideEffects": [
22
+ "**/*.css"
23
+ ],
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "dev": "vite",
29
+ "build": "tsc && vite build",
30
+ "test": "vitest",
31
+ "test:run": "vitest run",
32
+ "test:coverage": "vitest run --coverage",
33
+ "lint": "eslint src --ext ts,tsx",
34
+ "typecheck": "tsc --noEmit",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "keywords": [
38
+ "react",
39
+ "image-viewer",
40
+ "lightbox",
41
+ "modal",
42
+ "gallery",
43
+ "zoom",
44
+ "pan",
45
+ "pinch-to-zoom",
46
+ "typescript",
47
+ "accessible",
48
+ "nextjs",
49
+ "ssr"
50
+ ],
51
+ "author": "",
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": ""
56
+ },
57
+ "bugs": {
58
+ "url": ""
59
+ },
60
+ "homepage": "",
61
+ "peerDependencies": {
62
+ "react": ">=18.0.0",
63
+ "react-dom": ">=18.0.0"
64
+ },
65
+ "devDependencies": {
66
+ "@testing-library/jest-dom": "^6.4.2",
67
+ "@testing-library/react": "^14.2.1",
68
+ "@testing-library/user-event": "^14.5.2",
69
+ "@types/react": "^18.2.64",
70
+ "@types/react-dom": "^18.2.21",
71
+ "@vitejs/plugin-react": "^4.2.1",
72
+ "jsdom": "^24.0.0",
73
+ "react": "^18.2.0",
74
+ "react-dom": "^18.2.0",
75
+ "typescript": "^5.4.2",
76
+ "vite": "^5.1.6",
77
+ "vite-plugin-dts": "^3.7.3",
78
+ "vitest": "^1.3.1"
79
+ }
80
+ }
81
+