@cjboco/cj-image-flip-previewer 1.1.1 → 1.1.3

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 CHANGED
@@ -9,8 +9,8 @@ A React component for interactive image previews. Zero dependencies beyond React
9
9
  **[Live Demo](https://cjboco.github.io/cj-image-flip-previewer/)**
10
10
 
11
11
  One component, two modes:
12
- - **`mode="position"`** Image changes based on horizontal mouse/touch position (360-degree product views)
13
- - **`mode="hover"`** Images auto-cycle on a timer when hovered (video thumbnail previews)
12
+ - **`mode="position"`** - Image changes based on horizontal mouse/touch position (360-degree product views)
13
+ - **`mode="hover"`** - Images auto-cycle on a timer when hovered (video thumbnail previews)
14
14
 
15
15
  ## Install
16
16
 
@@ -143,16 +143,16 @@ function ControlledPreviewer() {
143
143
  | `images` | `FlipPreviewerImage[]` | *required* | Array of images |
144
144
  | `width` | `number \| string` | `"100%"` | Container width |
145
145
  | `height` | `number \| string` | `"100%"` | Container height |
146
- | `delay` | `number` | | Ms between frames (hover mode only). Omit for max speed (requestAnimationFrame). |
146
+ | `delay` | `number` | - | Ms between frames (hover mode only). Omit for max speed (requestAnimationFrame). |
147
147
  | `autoPlay` | `boolean` | `false` | Auto-start animation (hover mode only) |
148
148
  | `showProgress` | `boolean` | `true` | Show preload progress bar (hover mode only) |
149
149
  | `showCursor` | `boolean` | `true` | Show horizontal resize cursor (position mode only) |
150
150
  | `debug` | `boolean` | `false` | Show debug overlay (position mode only) |
151
- | `className` | `string` | | Additional CSS class(es) |
152
- | `style` | `CSSProperties` | | Additional inline styles |
153
- | `onIndexChange` | `(index: number) => void` | | Called when the active image changes |
154
- | `onImagesLoaded` | `() => void` | | Called when all images finish preloading (hover mode only) |
155
- | `ref` | `Ref<FlipPreviewerRef>` | | Imperative handle for start/pause (hover mode only) |
151
+ | `className` | `string` | - | Additional CSS class(es) |
152
+ | `style` | `CSSProperties` | - | Additional inline styles |
153
+ | `onIndexChange` | `(index: number) => void` | - | Called when the active image changes |
154
+ | `onImagesLoaded` | `() => void` | - | Called when all images finish preloading (hover mode only) |
155
+ | `ref` | `Ref<FlipPreviewerRef>` | - | Imperative handle for start/pause (hover mode only) |
156
156
 
157
157
  ### FlipPreviewerImage
158
158
 
@@ -186,7 +186,7 @@ import "@cjboco/cj-image-flip-previewer/styles.css";
186
186
 
187
187
  ### Using with Tailwind CSS
188
188
 
189
- Since the component styles are in the `components` layer, Tailwind utility classes automatically take priority no `!important` needed. Just add classes via the `className` prop:
189
+ Since the component styles are in the `components` layer, Tailwind utility classes automatically take priority - no `!important` needed. Just add classes via the `className` prop:
190
190
 
191
191
  ```tsx
192
192
  <FlipPreviewer
@@ -257,4 +257,4 @@ BSD-3-Clause
257
257
 
258
258
  ## Author
259
259
 
260
- Doug Jones [Creative Juices, Bo. Co.](https://www.cjboco.com)
260
+ Doug Jones - [Creative Juices, Bo. Co.](https://www.cjboco.com)
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/FlipPreviewer.tsx"],"sourcesContent":["export { FlipPreviewer } from \"./FlipPreviewer\";\nexport type {\n FlipPreviewerProps,\n FlipPreviewerImage,\n FlipPreviewerRef,\n} from \"./FlipPreviewer\";\n","import {\n\ttype CSSProperties,\n\ttype Ref,\n\tuseCallback,\n\tuseEffect,\n\tuseImperativeHandle,\n\tuseRef,\n\tuseState,\n} from \"react\";\n\nexport interface FlipPreviewerImage {\n\t/** Image source URL */\n\tsrc: string;\n\t/** Alt text for the image */\n\talt?: string;\n\t/** Optional link URL — clicking the image navigates here */\n\thref?: string;\n\t/** Link title attribute */\n\ttitle?: string;\n\t/** Link target attribute (e.g., \"_blank\") */\n\ttarget?: string;\n\t/** Link rel attribute (e.g., \"noopener noreferrer\") */\n\trel?: string;\n}\n\nexport interface FlipPreviewerRef {\n\t/** Start the hover animation (only applies in \"hover\" mode) */\n\tstart: () => void;\n\t/** Pause the animation and reset to the first frame (only applies in \"hover\" mode) */\n\tpause: () => void;\n}\n\nexport interface FlipPreviewerProps {\n\t/**\n\t * How images cycle:\n\t * - `\"position\"` — image changes based on horizontal mouse/touch position (default)\n\t * - `\"hover\"` — images auto-cycle on a timer when hovered\n\t */\n\tmode?: \"position\" | \"hover\";\n\t/** Array of images to flip through */\n\timages: FlipPreviewerImage[];\n\t/** Width of the component (CSS value). Omit to fill parent at 100%. */\n\twidth?: number | string;\n\t/** Height of the component (CSS value). Omit to fill parent at 100%. */\n\theight?: number | string;\n\t/**\n\t * How images fit within the container:\n\t * - `\"cover\"` — image covers the entire container, may crop (default)\n\t * - `\"contain\"` — image fits entirely within the container, may letterbox\n\t */\n\tfit?: \"cover\" | \"contain\";\n\t/** Delay in ms between frame transitions — only used in \"hover\" mode. Omit for max speed (requestAnimationFrame). */\n\tdelay?: number;\n\t/** Start animating automatically without hover — only used in \"hover\" mode (default: false) */\n\tautoPlay?: boolean;\n\t/** Show a progress bar while images preload — only used in \"hover\" mode (default: true) */\n\tshowProgress?: boolean;\n\t/** Show a horizontal resize cursor when in \"position\" mode (default: true) */\n\tshowCursor?: boolean;\n\t/** Show debug overlay with mouse coordinates and position info — only used in \"position\" mode */\n\tdebug?: boolean;\n\t/** Additional CSS class name(s) for the container */\n\tclassName?: string;\n\t/** Additional inline styles for the container */\n\tstyle?: CSSProperties;\n\t/** Callback fired when the active image index changes */\n\tonIndexChange?: (index: number) => void;\n\t/** Callback fired when all images have finished preloading — only used in \"hover\" mode */\n\tonImagesLoaded?: () => void;\n\t/** Imperative handle ref for start/pause control in \"hover\" mode */\n\tref?: Ref<FlipPreviewerRef>;\n}\n\nexport function FlipPreviewer({\n\tmode = \"position\",\n\timages,\n\twidth,\n\theight,\n\tfit = \"cover\",\n\tdelay,\n\tautoPlay = false,\n\tshowProgress = true,\n\tshowCursor = true,\n\tdebug = false,\n\tclassName,\n\tstyle,\n\tonIndexChange,\n\tonImagesLoaded,\n\tref,\n}: FlipPreviewerProps) {\n\tconst containerRef = useRef<HTMLElement>(null);\n\tconst timerRef = useRef<ReturnType<typeof setTimeout> | number | null>(null);\n\n\tconst [activeIndex, setActiveIndex] = useState(0);\n\tconst [debugInfo, setDebugInfo] = useState(\"\");\n\tconst [loadedCount, setLoadedCount] = useState(0);\n\tconst [allLoaded, setAllLoaded] = useState(false);\n\tconst [isPointerDown, setIsPointerDown] = useState(false);\n\n\tconst activeIndexRef = useRef(0);\n\tconst isPlayingRef = useRef(false);\n\n\t// ── Preload images (hover mode) ──────────────────────────────\n\tuseEffect(() => {\n\t\tif (mode !== \"hover\" || images.length === 0) {\n\t\t\tsetAllLoaded(true);\n\t\t\treturn;\n\t\t}\n\n\t\tlet loaded = 0;\n\t\tsetLoadedCount(0);\n\t\tsetAllLoaded(false);\n\n\t\tconst total = images.length;\n\t\timages.forEach(({ src }) => {\n\t\t\tconst img = new Image();\n\t\t\timg.onload = img.onerror = () => {\n\t\t\t\tloaded++;\n\t\t\t\tsetLoadedCount(loaded);\n\t\t\t\tif (loaded >= total) {\n\t\t\t\t\tsetAllLoaded(true);\n\t\t\t\t\tonImagesLoaded?.();\n\t\t\t\t}\n\t\t\t};\n\t\t\timg.src = src;\n\t\t});\n\t}, [mode, images, onImagesLoaded]);\n\n\t// ── Hover mode: animation ────────────────────────────────────\n\tconst clearTimer = useCallback(() => {\n\t\tif (timerRef.current !== null) {\n\t\t\tif (delay != null) {\n\t\t\t\tclearTimeout(timerRef.current as ReturnType<typeof setTimeout>);\n\t\t\t} else {\n\t\t\t\tcancelAnimationFrame(timerRef.current as number);\n\t\t\t}\n\t\t\ttimerRef.current = null;\n\t\t}\n\t}, [delay]);\n\n\tconst advanceFrame = useCallback(() => {\n\t\tif (!isPlayingRef.current) return;\n\t\tif (images.length <= 1) return;\n\n\t\tconst next =\n\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t? 0\n\t\t\t\t: activeIndexRef.current + 1;\n\n\t\tactiveIndexRef.current = next;\n\t\tsetActiveIndex(next);\n\t\tonIndexChange?.(next);\n\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [images.length, delay, onIndexChange]);\n\n\tconst startAnimation = useCallback(() => {\n\t\tif (!allLoaded || images.length <= 1) return;\n\t\tisPlayingRef.current = true;\n\t\tclearTimer();\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [allLoaded, images.length, delay, advanceFrame, clearTimer]);\n\n\tconst stopAnimation = useCallback(() => {\n\t\tisPlayingRef.current = false;\n\t\tclearTimer();\n\t\tactiveIndexRef.current = 0;\n\t\tsetActiveIndex(0);\n\t\tonIndexChange?.(0);\n\t}, [clearTimer, onIndexChange]);\n\n\t// Auto-play on mount if enabled (hover mode)\n\tuseEffect(() => {\n\t\tif (mode === \"hover\" && autoPlay && allLoaded) {\n\t\t\tstartAnimation();\n\t\t}\n\t\treturn () => clearTimer();\n\t}, [mode, autoPlay, allLoaded, startAnimation, clearTimer]);\n\n\t// Expose start/pause via ref (hover mode)\n\tuseImperativeHandle(\n\t\tref,\n\t\t() => ({\n\t\t\tstart: startAnimation,\n\t\t\tpause: stopAnimation,\n\t\t}),\n\t\t[startAnimation, stopAnimation],\n\t);\n\n\tconst handleMouseEnter = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstartAnimation();\n\t\t}\n\t}, [mode, autoPlay, startAnimation]);\n\n\tconst handleMouseLeave = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstopAnimation();\n\t\t}\n\t\tsetIsPointerDown(false);\n\t}, [mode, autoPlay, stopAnimation]);\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(true);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t// Capture pointer so move events keep firing on touch devices\n\t\t\t\t(e.target as HTMLElement).setPointerCapture(e.pointerId);\n\n\t\t\t\t// Update index immediately on press\n\t\t\t\tconst container = containerRef.current;\n\t\t\t\tif (container && images.length > 0) {\n\t\t\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\t\t\tconst x = e.clientX - rect.left;\n\t\t\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\t\t\tsetActiveIndex(pos);\n\t\t\t\t\t\tonIndexChange?.(pos);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, onIndexChange],\n\t);\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(false);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t(e.target as HTMLElement).releasePointerCapture(e.pointerId);\n\t\t\t}\n\t\t},\n\t\t[mode],\n\t);\n\n\t// ── Position mode: mouse-position-based ──────────────────────\n\tconst handlePointerMove = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tif (mode !== \"position\") return;\n\n\t\t\tconst container = containerRef.current;\n\t\t\tif (!container || images.length === 0) return;\n\n\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\tconst x = e.clientX - rect.left;\n\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\tsetActiveIndex(pos);\n\t\t\t\tonIndexChange?.(pos);\n\t\t\t}\n\n\t\t\tif (debug) {\n\t\t\t\tconst y = e.clientY - rect.top;\n\t\t\t\tsetDebugInfo(\n\t\t\t\t\t`x:${Math.round(x)}, y:${Math.round(y)}, img:${pos + 1}/${images.length}, w:${Math.round(rect.width)}, h:${Math.round(rect.height)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, debug, onIndexChange],\n\t);\n\n\t// Reset index if images change\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: intentional trigger when images change\n\tuseEffect(() => {\n\t\tsetActiveIndex(0);\n\t\tactiveIndexRef.current = 0;\n\t}, [images]);\n\n\t// ── Keyboard navigation ─────────────────────────────────────\n\tconst handleKeyDown = useCallback(\n\t\t(e: React.KeyboardEvent<HTMLElement>) => {\n\t\t\tif (images.length <= 1) return;\n\n\t\t\tlet next: number | null = null;\n\n\t\t\tif (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t\t\t? 0\n\t\t\t\t\t\t: activeIndexRef.current + 1;\n\t\t\t} else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current - 1 < 0\n\t\t\t\t\t\t? images.length - 1\n\t\t\t\t\t\t: activeIndexRef.current - 1;\n\t\t\t} else if (e.key === \"Home\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = 0;\n\t\t\t} else if (e.key === \"End\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = images.length - 1;\n\t\t\t}\n\n\t\t\tif (next !== null && next !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = next;\n\t\t\t\tsetActiveIndex(next);\n\t\t\t\tonIndexChange?.(next);\n\t\t\t}\n\t\t},\n\t\t[images.length, onIndexChange],\n\t);\n\n\t// ── Render ───────────────────────────────────────────────────\n\tif (images.length === 0) return null;\n\n\tconst activeImage = images[activeIndex];\n\tconst hasLink = !!activeImage.href;\n\n\tconst containerStyle: CSSProperties = {\n\t\tposition: \"relative\",\n\t\twidth: width ?? \"100%\",\n\t\theight: height ?? \"100%\",\n\t\toverflow: \"hidden\",\n\t\tcursor:\n\t\t\tisPointerDown || !(mode === \"position\" && showCursor)\n\t\t\t\t? hasLink\n\t\t\t\t\t? \"pointer\"\n\t\t\t\t\t: \"default\"\n\t\t\t\t: \"ew-resize\",\n\t\ttouchAction: mode === \"position\" ? \"none\" : undefined,\n\t\t...style,\n\t};\n\n\tconst imgElement = (\n\t\t<img\n\t\t\tsrc={activeImage.src}\n\t\t\talt={activeImage.alt ?? \"\"}\n\t\t\tclassName=\"cj-flip-previewer__img\"\n\t\t\tstyle={{ objectFit: fit }}\n\t\t\tdraggable={false}\n\t\t/>\n\t);\n\n\tconst content = hasLink ? (\n\t\t<a\n\t\t\thref={activeImage.href}\n\t\t\ttitle={activeImage.title}\n\t\t\ttarget={activeImage.target}\n\t\t\trel={activeImage.rel}\n\t\t\tclassName=\"cj-flip-previewer__link\"\n\t\t>\n\t\t\t{imgElement}\n\t\t</a>\n\t) : (\n\t\timgElement\n\t);\n\n\tconst progressPct =\n\t\timages.length > 0 ? (loadedCount / images.length) * 100 : 0;\n\n\treturn (\n\t\t<section\n\t\t\tref={containerRef}\n\t\t\taria-roledescription=\"image flipper\"\n\t\t\taria-label={`Image ${activeIndex + 1} of ${images.length}`}\n\t\t\ttabIndex={0}\n\t\t\tclassName={`cj-flip-previewer${className ? ` ${className}` : \"\"}`}\n\t\t\tstyle={containerStyle}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tonPointerUp={handlePointerUp}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t>\n\t\t\t{content}\n\n\t\t\t<div\n\t\t\t\tclassName=\"cj-flip-previewer__sr-only\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t>\n\t\t\t\t{activeImage.alt\n\t\t\t\t\t? `Image ${activeIndex + 1} of ${images.length}: ${activeImage.alt}`\n\t\t\t\t\t: `Image ${activeIndex + 1} of ${images.length}`}\n\t\t\t</div>\n\n\t\t\t{mode === \"hover\" && showProgress && !allLoaded && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"cj-flip-previewer__progress\"\n\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\taria-valuenow={Math.round(progressPct)}\n\t\t\t\t\taria-valuemin={0}\n\t\t\t\t\taria-valuemax={100}\n\t\t\t\t\taria-label=\"Loading images\"\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"cj-flip-previewer__progress-bar\"\n\t\t\t\t\t\tstyle={{ width: `${progressPct}%` }}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{mode === \"position\" && debug && (\n\t\t\t\t<div className=\"cj-flip-previewer__debug\" aria-hidden=\"true\">\n\t\t\t\t\t{debugInfo}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</section>\n\t);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/FlipPreviewer.tsx"],"sourcesContent":["export { FlipPreviewer } from \"./FlipPreviewer\";\nexport type {\n FlipPreviewerProps,\n FlipPreviewerImage,\n FlipPreviewerRef,\n} from \"./FlipPreviewer\";\n","import {\n\ttype CSSProperties,\n\ttype Ref,\n\tuseCallback,\n\tuseEffect,\n\tuseImperativeHandle,\n\tuseRef,\n\tuseState,\n} from \"react\";\n\nexport interface FlipPreviewerImage {\n\t/** Image source URL */\n\tsrc: string;\n\t/** Alt text for the image */\n\talt?: string;\n\t/** Optional link URL - clicking the image navigates here */\n\thref?: string;\n\t/** Link title attribute */\n\ttitle?: string;\n\t/** Link target attribute (e.g., \"_blank\") */\n\ttarget?: string;\n\t/** Link rel attribute (e.g., \"noopener noreferrer\") */\n\trel?: string;\n}\n\nexport interface FlipPreviewerRef {\n\t/** Start the hover animation (only applies in \"hover\" mode) */\n\tstart: () => void;\n\t/** Pause the animation and reset to the first frame (only applies in \"hover\" mode) */\n\tpause: () => void;\n}\n\nexport interface FlipPreviewerProps {\n\t/**\n\t * How images cycle:\n\t * - `\"position\"` - image changes based on horizontal mouse/touch position (default)\n\t * - `\"hover\"` - images auto-cycle on a timer when hovered\n\t */\n\tmode?: \"position\" | \"hover\";\n\t/** Array of images to flip through */\n\timages: FlipPreviewerImage[];\n\t/** Width of the component (CSS value). Omit to fill parent at 100%. */\n\twidth?: number | string;\n\t/** Height of the component (CSS value). Omit to fill parent at 100%. */\n\theight?: number | string;\n\t/**\n\t * How images fit within the container:\n\t * - `\"cover\"` - image covers the entire container, may crop (default)\n\t * - `\"contain\"` - image fits entirely within the container, may letterbox\n\t */\n\tfit?: \"cover\" | \"contain\";\n\t/** Delay in ms between frame transitions - only used in \"hover\" mode. Omit for max speed (requestAnimationFrame). */\n\tdelay?: number;\n\t/** Start animating automatically without hover - only used in \"hover\" mode (default: false) */\n\tautoPlay?: boolean;\n\t/** Show a progress bar while images preload - only used in \"hover\" mode (default: true) */\n\tshowProgress?: boolean;\n\t/** Show a horizontal resize cursor when in \"position\" mode (default: true) */\n\tshowCursor?: boolean;\n\t/** Show debug overlay with mouse coordinates and position info - only used in \"position\" mode */\n\tdebug?: boolean;\n\t/** Additional CSS class name(s) for the container */\n\tclassName?: string;\n\t/** Additional inline styles for the container */\n\tstyle?: CSSProperties;\n\t/** Callback fired when the active image index changes */\n\tonIndexChange?: (index: number) => void;\n\t/** Callback fired when all images have finished preloading - only used in \"hover\" mode */\n\tonImagesLoaded?: () => void;\n\t/** Imperative handle ref for start/pause control in \"hover\" mode */\n\tref?: Ref<FlipPreviewerRef>;\n}\n\nexport function FlipPreviewer({\n\tmode = \"position\",\n\timages,\n\twidth,\n\theight,\n\tfit = \"cover\",\n\tdelay,\n\tautoPlay = false,\n\tshowProgress = true,\n\tshowCursor = true,\n\tdebug = false,\n\tclassName,\n\tstyle,\n\tonIndexChange,\n\tonImagesLoaded,\n\tref,\n}: FlipPreviewerProps) {\n\tconst containerRef = useRef<HTMLElement>(null);\n\tconst timerRef = useRef<ReturnType<typeof setTimeout> | number | null>(null);\n\n\tconst [activeIndex, setActiveIndex] = useState(0);\n\tconst [debugInfo, setDebugInfo] = useState(\"\");\n\tconst [loadedCount, setLoadedCount] = useState(0);\n\tconst [allLoaded, setAllLoaded] = useState(false);\n\tconst [isPointerDown, setIsPointerDown] = useState(false);\n\n\tconst activeIndexRef = useRef(0);\n\tconst isPlayingRef = useRef(false);\n\n\t// ── Preload images (hover mode) ──────────────────────────────\n\tuseEffect(() => {\n\t\tif (mode !== \"hover\" || images.length === 0) {\n\t\t\tsetAllLoaded(true);\n\t\t\treturn;\n\t\t}\n\n\t\tlet loaded = 0;\n\t\tsetLoadedCount(0);\n\t\tsetAllLoaded(false);\n\n\t\tconst total = images.length;\n\t\timages.forEach(({ src }) => {\n\t\t\tconst img = new Image();\n\t\t\timg.onload = img.onerror = () => {\n\t\t\t\tloaded++;\n\t\t\t\tsetLoadedCount(loaded);\n\t\t\t\tif (loaded >= total) {\n\t\t\t\t\tsetAllLoaded(true);\n\t\t\t\t\tonImagesLoaded?.();\n\t\t\t\t}\n\t\t\t};\n\t\t\timg.src = src;\n\t\t});\n\t}, [mode, images, onImagesLoaded]);\n\n\t// ── Hover mode: animation ────────────────────────────────────\n\tconst clearTimer = useCallback(() => {\n\t\tif (timerRef.current !== null) {\n\t\t\tif (delay != null) {\n\t\t\t\tclearTimeout(timerRef.current as ReturnType<typeof setTimeout>);\n\t\t\t} else {\n\t\t\t\tcancelAnimationFrame(timerRef.current as number);\n\t\t\t}\n\t\t\ttimerRef.current = null;\n\t\t}\n\t}, [delay]);\n\n\tconst advanceFrame = useCallback(() => {\n\t\tif (!isPlayingRef.current) return;\n\t\tif (images.length <= 1) return;\n\n\t\tconst next =\n\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t? 0\n\t\t\t\t: activeIndexRef.current + 1;\n\n\t\tactiveIndexRef.current = next;\n\t\tsetActiveIndex(next);\n\t\tonIndexChange?.(next);\n\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [images.length, delay, onIndexChange]);\n\n\tconst startAnimation = useCallback(() => {\n\t\tif (!allLoaded || images.length <= 1) return;\n\t\tisPlayingRef.current = true;\n\t\tclearTimer();\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [allLoaded, images.length, delay, advanceFrame, clearTimer]);\n\n\tconst stopAnimation = useCallback(() => {\n\t\tisPlayingRef.current = false;\n\t\tclearTimer();\n\t\tactiveIndexRef.current = 0;\n\t\tsetActiveIndex(0);\n\t\tonIndexChange?.(0);\n\t}, [clearTimer, onIndexChange]);\n\n\t// Auto-play on mount if enabled (hover mode)\n\tuseEffect(() => {\n\t\tif (mode === \"hover\" && autoPlay && allLoaded) {\n\t\t\tstartAnimation();\n\t\t}\n\t\treturn () => clearTimer();\n\t}, [mode, autoPlay, allLoaded, startAnimation, clearTimer]);\n\n\t// Expose start/pause via ref (hover mode)\n\tuseImperativeHandle(\n\t\tref,\n\t\t() => ({\n\t\t\tstart: startAnimation,\n\t\t\tpause: stopAnimation,\n\t\t}),\n\t\t[startAnimation, stopAnimation],\n\t);\n\n\tconst handleMouseEnter = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstartAnimation();\n\t\t}\n\t}, [mode, autoPlay, startAnimation]);\n\n\tconst handleMouseLeave = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstopAnimation();\n\t\t}\n\t\tsetIsPointerDown(false);\n\t}, [mode, autoPlay, stopAnimation]);\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(true);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t// Capture pointer so move events keep firing on touch devices\n\t\t\t\t(e.target as HTMLElement).setPointerCapture(e.pointerId);\n\n\t\t\t\t// Update index immediately on press\n\t\t\t\tconst container = containerRef.current;\n\t\t\t\tif (container && images.length > 0) {\n\t\t\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\t\t\tconst x = e.clientX - rect.left;\n\t\t\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\t\t\tsetActiveIndex(pos);\n\t\t\t\t\t\tonIndexChange?.(pos);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, onIndexChange],\n\t);\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(false);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t(e.target as HTMLElement).releasePointerCapture(e.pointerId);\n\t\t\t}\n\t\t},\n\t\t[mode],\n\t);\n\n\t// ── Position mode: mouse-position-based ──────────────────────\n\tconst handlePointerMove = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tif (mode !== \"position\") return;\n\n\t\t\tconst container = containerRef.current;\n\t\t\tif (!container || images.length === 0) return;\n\n\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\tconst x = e.clientX - rect.left;\n\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\tsetActiveIndex(pos);\n\t\t\t\tonIndexChange?.(pos);\n\t\t\t}\n\n\t\t\tif (debug) {\n\t\t\t\tconst y = e.clientY - rect.top;\n\t\t\t\tsetDebugInfo(\n\t\t\t\t\t`x:${Math.round(x)}, y:${Math.round(y)}, img:${pos + 1}/${images.length}, w:${Math.round(rect.width)}, h:${Math.round(rect.height)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, debug, onIndexChange],\n\t);\n\n\t// Reset index if images change\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: intentional trigger when images change\n\tuseEffect(() => {\n\t\tsetActiveIndex(0);\n\t\tactiveIndexRef.current = 0;\n\t}, [images]);\n\n\t// ── Keyboard navigation ─────────────────────────────────────\n\tconst handleKeyDown = useCallback(\n\t\t(e: React.KeyboardEvent<HTMLElement>) => {\n\t\t\tif (images.length <= 1) return;\n\n\t\t\tlet next: number | null = null;\n\n\t\t\tif (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t\t\t? 0\n\t\t\t\t\t\t: activeIndexRef.current + 1;\n\t\t\t} else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current - 1 < 0\n\t\t\t\t\t\t? images.length - 1\n\t\t\t\t\t\t: activeIndexRef.current - 1;\n\t\t\t} else if (e.key === \"Home\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = 0;\n\t\t\t} else if (e.key === \"End\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = images.length - 1;\n\t\t\t}\n\n\t\t\tif (next !== null && next !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = next;\n\t\t\t\tsetActiveIndex(next);\n\t\t\t\tonIndexChange?.(next);\n\t\t\t}\n\t\t},\n\t\t[images.length, onIndexChange],\n\t);\n\n\t// ── Render ───────────────────────────────────────────────────\n\tif (images.length === 0) return null;\n\n\tconst activeImage = images[activeIndex];\n\tconst hasLink = !!activeImage.href;\n\n\tconst containerStyle: CSSProperties = {\n\t\tposition: \"relative\",\n\t\twidth: width ?? \"100%\",\n\t\theight: height ?? \"100%\",\n\t\toverflow: \"hidden\",\n\t\tcursor:\n\t\t\tisPointerDown || !(mode === \"position\" && showCursor)\n\t\t\t\t? hasLink\n\t\t\t\t\t? \"pointer\"\n\t\t\t\t\t: \"default\"\n\t\t\t\t: \"ew-resize\",\n\t\ttouchAction: mode === \"position\" ? \"none\" : undefined,\n\t\t...style,\n\t};\n\n\tconst imgElement = (\n\t\t<img\n\t\t\tsrc={activeImage.src}\n\t\t\talt={activeImage.alt ?? \"\"}\n\t\t\tclassName=\"cj-flip-previewer__img\"\n\t\t\tstyle={{ objectFit: fit }}\n\t\t\tdraggable={false}\n\t\t/>\n\t);\n\n\tconst content = hasLink ? (\n\t\t<a\n\t\t\thref={activeImage.href}\n\t\t\ttitle={activeImage.title}\n\t\t\ttarget={activeImage.target}\n\t\t\trel={activeImage.rel}\n\t\t\tclassName=\"cj-flip-previewer__link\"\n\t\t>\n\t\t\t{imgElement}\n\t\t</a>\n\t) : (\n\t\timgElement\n\t);\n\n\tconst progressPct =\n\t\timages.length > 0 ? (loadedCount / images.length) * 100 : 0;\n\n\treturn (\n\t\t<section\n\t\t\tref={containerRef}\n\t\t\taria-roledescription=\"image flipper\"\n\t\t\taria-label={`Image ${activeIndex + 1} of ${images.length}`}\n\t\t\ttabIndex={0}\n\t\t\tclassName={`cj-flip-previewer${className ? ` ${className}` : \"\"}`}\n\t\t\tstyle={containerStyle}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tonPointerUp={handlePointerUp}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t>\n\t\t\t{content}\n\n\t\t\t<div\n\t\t\t\tclassName=\"cj-flip-previewer__sr-only\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t>\n\t\t\t\t{activeImage.alt\n\t\t\t\t\t? `Image ${activeIndex + 1} of ${images.length}: ${activeImage.alt}`\n\t\t\t\t\t: `Image ${activeIndex + 1} of ${images.length}`}\n\t\t\t</div>\n\n\t\t\t{mode === \"hover\" && showProgress && !allLoaded && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"cj-flip-previewer__progress\"\n\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\taria-valuenow={Math.round(progressPct)}\n\t\t\t\t\taria-valuemin={0}\n\t\t\t\t\taria-valuemax={100}\n\t\t\t\t\taria-label=\"Loading images\"\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"cj-flip-previewer__progress-bar\"\n\t\t\t\t\t\tstyle={{ width: `${progressPct}%` }}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{mode === \"position\" && debug && (\n\t\t\t\t<div className=\"cj-flip-previewer__debug\" aria-hidden=\"true\">\n\t\t\t\t\t{debugInfo}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</section>\n\t);\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { CSSProperties, Ref } from 'react';
3
3
 
4
4
  interface FlipPreviewerImage {
@@ -6,7 +6,7 @@ interface FlipPreviewerImage {
6
6
  src: string;
7
7
  /** Alt text for the image */
8
8
  alt?: string;
9
- /** Optional link URL clicking the image navigates here */
9
+ /** Optional link URL - clicking the image navigates here */
10
10
  href?: string;
11
11
  /** Link title attribute */
12
12
  title?: string;
@@ -24,8 +24,8 @@ interface FlipPreviewerRef {
24
24
  interface FlipPreviewerProps {
25
25
  /**
26
26
  * How images cycle:
27
- * - `"position"` image changes based on horizontal mouse/touch position (default)
28
- * - `"hover"` images auto-cycle on a timer when hovered
27
+ * - `"position"` - image changes based on horizontal mouse/touch position (default)
28
+ * - `"hover"` - images auto-cycle on a timer when hovered
29
29
  */
30
30
  mode?: "position" | "hover";
31
31
  /** Array of images to flip through */
@@ -36,19 +36,19 @@ interface FlipPreviewerProps {
36
36
  height?: number | string;
37
37
  /**
38
38
  * How images fit within the container:
39
- * - `"cover"` image covers the entire container, may crop (default)
40
- * - `"contain"` image fits entirely within the container, may letterbox
39
+ * - `"cover"` - image covers the entire container, may crop (default)
40
+ * - `"contain"` - image fits entirely within the container, may letterbox
41
41
  */
42
42
  fit?: "cover" | "contain";
43
- /** Delay in ms between frame transitions only used in "hover" mode. Omit for max speed (requestAnimationFrame). */
43
+ /** Delay in ms between frame transitions - only used in "hover" mode. Omit for max speed (requestAnimationFrame). */
44
44
  delay?: number;
45
- /** Start animating automatically without hover only used in "hover" mode (default: false) */
45
+ /** Start animating automatically without hover - only used in "hover" mode (default: false) */
46
46
  autoPlay?: boolean;
47
- /** Show a progress bar while images preload only used in "hover" mode (default: true) */
47
+ /** Show a progress bar while images preload - only used in "hover" mode (default: true) */
48
48
  showProgress?: boolean;
49
49
  /** Show a horizontal resize cursor when in "position" mode (default: true) */
50
50
  showCursor?: boolean;
51
- /** Show debug overlay with mouse coordinates and position info only used in "position" mode */
51
+ /** Show debug overlay with mouse coordinates and position info - only used in "position" mode */
52
52
  debug?: boolean;
53
53
  /** Additional CSS class name(s) for the container */
54
54
  className?: string;
@@ -56,11 +56,11 @@ interface FlipPreviewerProps {
56
56
  style?: CSSProperties;
57
57
  /** Callback fired when the active image index changes */
58
58
  onIndexChange?: (index: number) => void;
59
- /** Callback fired when all images have finished preloading only used in "hover" mode */
59
+ /** Callback fired when all images have finished preloading - only used in "hover" mode */
60
60
  onImagesLoaded?: () => void;
61
61
  /** Imperative handle ref for start/pause control in "hover" mode */
62
62
  ref?: Ref<FlipPreviewerRef>;
63
63
  }
64
- declare function FlipPreviewer({ mode, images, width, height, fit, delay, autoPlay, showProgress, showCursor, debug, className, style, onIndexChange, onImagesLoaded, ref, }: FlipPreviewerProps): react_jsx_runtime.JSX.Element | null;
64
+ declare function FlipPreviewer({ mode, images, width, height, fit, delay, autoPlay, showProgress, showCursor, debug, className, style, onIndexChange, onImagesLoaded, ref, }: FlipPreviewerProps): react.JSX.Element | null;
65
65
 
66
66
  export { FlipPreviewer, type FlipPreviewerImage, type FlipPreviewerProps, type FlipPreviewerRef };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { CSSProperties, Ref } from 'react';
3
3
 
4
4
  interface FlipPreviewerImage {
@@ -6,7 +6,7 @@ interface FlipPreviewerImage {
6
6
  src: string;
7
7
  /** Alt text for the image */
8
8
  alt?: string;
9
- /** Optional link URL clicking the image navigates here */
9
+ /** Optional link URL - clicking the image navigates here */
10
10
  href?: string;
11
11
  /** Link title attribute */
12
12
  title?: string;
@@ -24,8 +24,8 @@ interface FlipPreviewerRef {
24
24
  interface FlipPreviewerProps {
25
25
  /**
26
26
  * How images cycle:
27
- * - `"position"` image changes based on horizontal mouse/touch position (default)
28
- * - `"hover"` images auto-cycle on a timer when hovered
27
+ * - `"position"` - image changes based on horizontal mouse/touch position (default)
28
+ * - `"hover"` - images auto-cycle on a timer when hovered
29
29
  */
30
30
  mode?: "position" | "hover";
31
31
  /** Array of images to flip through */
@@ -36,19 +36,19 @@ interface FlipPreviewerProps {
36
36
  height?: number | string;
37
37
  /**
38
38
  * How images fit within the container:
39
- * - `"cover"` image covers the entire container, may crop (default)
40
- * - `"contain"` image fits entirely within the container, may letterbox
39
+ * - `"cover"` - image covers the entire container, may crop (default)
40
+ * - `"contain"` - image fits entirely within the container, may letterbox
41
41
  */
42
42
  fit?: "cover" | "contain";
43
- /** Delay in ms between frame transitions only used in "hover" mode. Omit for max speed (requestAnimationFrame). */
43
+ /** Delay in ms between frame transitions - only used in "hover" mode. Omit for max speed (requestAnimationFrame). */
44
44
  delay?: number;
45
- /** Start animating automatically without hover only used in "hover" mode (default: false) */
45
+ /** Start animating automatically without hover - only used in "hover" mode (default: false) */
46
46
  autoPlay?: boolean;
47
- /** Show a progress bar while images preload only used in "hover" mode (default: true) */
47
+ /** Show a progress bar while images preload - only used in "hover" mode (default: true) */
48
48
  showProgress?: boolean;
49
49
  /** Show a horizontal resize cursor when in "position" mode (default: true) */
50
50
  showCursor?: boolean;
51
- /** Show debug overlay with mouse coordinates and position info only used in "position" mode */
51
+ /** Show debug overlay with mouse coordinates and position info - only used in "position" mode */
52
52
  debug?: boolean;
53
53
  /** Additional CSS class name(s) for the container */
54
54
  className?: string;
@@ -56,11 +56,11 @@ interface FlipPreviewerProps {
56
56
  style?: CSSProperties;
57
57
  /** Callback fired when the active image index changes */
58
58
  onIndexChange?: (index: number) => void;
59
- /** Callback fired when all images have finished preloading only used in "hover" mode */
59
+ /** Callback fired when all images have finished preloading - only used in "hover" mode */
60
60
  onImagesLoaded?: () => void;
61
61
  /** Imperative handle ref for start/pause control in "hover" mode */
62
62
  ref?: Ref<FlipPreviewerRef>;
63
63
  }
64
- declare function FlipPreviewer({ mode, images, width, height, fit, delay, autoPlay, showProgress, showCursor, debug, className, style, onIndexChange, onImagesLoaded, ref, }: FlipPreviewerProps): react_jsx_runtime.JSX.Element | null;
64
+ declare function FlipPreviewer({ mode, images, width, height, fit, delay, autoPlay, showProgress, showCursor, debug, className, style, onIndexChange, onImagesLoaded, ref, }: FlipPreviewerProps): react.JSX.Element | null;
65
65
 
66
66
  export { FlipPreviewer, type FlipPreviewerImage, type FlipPreviewerProps, type FlipPreviewerRef };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/FlipPreviewer.tsx"],"sourcesContent":["import {\n\ttype CSSProperties,\n\ttype Ref,\n\tuseCallback,\n\tuseEffect,\n\tuseImperativeHandle,\n\tuseRef,\n\tuseState,\n} from \"react\";\n\nexport interface FlipPreviewerImage {\n\t/** Image source URL */\n\tsrc: string;\n\t/** Alt text for the image */\n\talt?: string;\n\t/** Optional link URL — clicking the image navigates here */\n\thref?: string;\n\t/** Link title attribute */\n\ttitle?: string;\n\t/** Link target attribute (e.g., \"_blank\") */\n\ttarget?: string;\n\t/** Link rel attribute (e.g., \"noopener noreferrer\") */\n\trel?: string;\n}\n\nexport interface FlipPreviewerRef {\n\t/** Start the hover animation (only applies in \"hover\" mode) */\n\tstart: () => void;\n\t/** Pause the animation and reset to the first frame (only applies in \"hover\" mode) */\n\tpause: () => void;\n}\n\nexport interface FlipPreviewerProps {\n\t/**\n\t * How images cycle:\n\t * - `\"position\"` — image changes based on horizontal mouse/touch position (default)\n\t * - `\"hover\"` — images auto-cycle on a timer when hovered\n\t */\n\tmode?: \"position\" | \"hover\";\n\t/** Array of images to flip through */\n\timages: FlipPreviewerImage[];\n\t/** Width of the component (CSS value). Omit to fill parent at 100%. */\n\twidth?: number | string;\n\t/** Height of the component (CSS value). Omit to fill parent at 100%. */\n\theight?: number | string;\n\t/**\n\t * How images fit within the container:\n\t * - `\"cover\"` — image covers the entire container, may crop (default)\n\t * - `\"contain\"` — image fits entirely within the container, may letterbox\n\t */\n\tfit?: \"cover\" | \"contain\";\n\t/** Delay in ms between frame transitions — only used in \"hover\" mode. Omit for max speed (requestAnimationFrame). */\n\tdelay?: number;\n\t/** Start animating automatically without hover — only used in \"hover\" mode (default: false) */\n\tautoPlay?: boolean;\n\t/** Show a progress bar while images preload — only used in \"hover\" mode (default: true) */\n\tshowProgress?: boolean;\n\t/** Show a horizontal resize cursor when in \"position\" mode (default: true) */\n\tshowCursor?: boolean;\n\t/** Show debug overlay with mouse coordinates and position info — only used in \"position\" mode */\n\tdebug?: boolean;\n\t/** Additional CSS class name(s) for the container */\n\tclassName?: string;\n\t/** Additional inline styles for the container */\n\tstyle?: CSSProperties;\n\t/** Callback fired when the active image index changes */\n\tonIndexChange?: (index: number) => void;\n\t/** Callback fired when all images have finished preloading — only used in \"hover\" mode */\n\tonImagesLoaded?: () => void;\n\t/** Imperative handle ref for start/pause control in \"hover\" mode */\n\tref?: Ref<FlipPreviewerRef>;\n}\n\nexport function FlipPreviewer({\n\tmode = \"position\",\n\timages,\n\twidth,\n\theight,\n\tfit = \"cover\",\n\tdelay,\n\tautoPlay = false,\n\tshowProgress = true,\n\tshowCursor = true,\n\tdebug = false,\n\tclassName,\n\tstyle,\n\tonIndexChange,\n\tonImagesLoaded,\n\tref,\n}: FlipPreviewerProps) {\n\tconst containerRef = useRef<HTMLElement>(null);\n\tconst timerRef = useRef<ReturnType<typeof setTimeout> | number | null>(null);\n\n\tconst [activeIndex, setActiveIndex] = useState(0);\n\tconst [debugInfo, setDebugInfo] = useState(\"\");\n\tconst [loadedCount, setLoadedCount] = useState(0);\n\tconst [allLoaded, setAllLoaded] = useState(false);\n\tconst [isPointerDown, setIsPointerDown] = useState(false);\n\n\tconst activeIndexRef = useRef(0);\n\tconst isPlayingRef = useRef(false);\n\n\t// ── Preload images (hover mode) ──────────────────────────────\n\tuseEffect(() => {\n\t\tif (mode !== \"hover\" || images.length === 0) {\n\t\t\tsetAllLoaded(true);\n\t\t\treturn;\n\t\t}\n\n\t\tlet loaded = 0;\n\t\tsetLoadedCount(0);\n\t\tsetAllLoaded(false);\n\n\t\tconst total = images.length;\n\t\timages.forEach(({ src }) => {\n\t\t\tconst img = new Image();\n\t\t\timg.onload = img.onerror = () => {\n\t\t\t\tloaded++;\n\t\t\t\tsetLoadedCount(loaded);\n\t\t\t\tif (loaded >= total) {\n\t\t\t\t\tsetAllLoaded(true);\n\t\t\t\t\tonImagesLoaded?.();\n\t\t\t\t}\n\t\t\t};\n\t\t\timg.src = src;\n\t\t});\n\t}, [mode, images, onImagesLoaded]);\n\n\t// ── Hover mode: animation ────────────────────────────────────\n\tconst clearTimer = useCallback(() => {\n\t\tif (timerRef.current !== null) {\n\t\t\tif (delay != null) {\n\t\t\t\tclearTimeout(timerRef.current as ReturnType<typeof setTimeout>);\n\t\t\t} else {\n\t\t\t\tcancelAnimationFrame(timerRef.current as number);\n\t\t\t}\n\t\t\ttimerRef.current = null;\n\t\t}\n\t}, [delay]);\n\n\tconst advanceFrame = useCallback(() => {\n\t\tif (!isPlayingRef.current) return;\n\t\tif (images.length <= 1) return;\n\n\t\tconst next =\n\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t? 0\n\t\t\t\t: activeIndexRef.current + 1;\n\n\t\tactiveIndexRef.current = next;\n\t\tsetActiveIndex(next);\n\t\tonIndexChange?.(next);\n\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [images.length, delay, onIndexChange]);\n\n\tconst startAnimation = useCallback(() => {\n\t\tif (!allLoaded || images.length <= 1) return;\n\t\tisPlayingRef.current = true;\n\t\tclearTimer();\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [allLoaded, images.length, delay, advanceFrame, clearTimer]);\n\n\tconst stopAnimation = useCallback(() => {\n\t\tisPlayingRef.current = false;\n\t\tclearTimer();\n\t\tactiveIndexRef.current = 0;\n\t\tsetActiveIndex(0);\n\t\tonIndexChange?.(0);\n\t}, [clearTimer, onIndexChange]);\n\n\t// Auto-play on mount if enabled (hover mode)\n\tuseEffect(() => {\n\t\tif (mode === \"hover\" && autoPlay && allLoaded) {\n\t\t\tstartAnimation();\n\t\t}\n\t\treturn () => clearTimer();\n\t}, [mode, autoPlay, allLoaded, startAnimation, clearTimer]);\n\n\t// Expose start/pause via ref (hover mode)\n\tuseImperativeHandle(\n\t\tref,\n\t\t() => ({\n\t\t\tstart: startAnimation,\n\t\t\tpause: stopAnimation,\n\t\t}),\n\t\t[startAnimation, stopAnimation],\n\t);\n\n\tconst handleMouseEnter = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstartAnimation();\n\t\t}\n\t}, [mode, autoPlay, startAnimation]);\n\n\tconst handleMouseLeave = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstopAnimation();\n\t\t}\n\t\tsetIsPointerDown(false);\n\t}, [mode, autoPlay, stopAnimation]);\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(true);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t// Capture pointer so move events keep firing on touch devices\n\t\t\t\t(e.target as HTMLElement).setPointerCapture(e.pointerId);\n\n\t\t\t\t// Update index immediately on press\n\t\t\t\tconst container = containerRef.current;\n\t\t\t\tif (container && images.length > 0) {\n\t\t\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\t\t\tconst x = e.clientX - rect.left;\n\t\t\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\t\t\tsetActiveIndex(pos);\n\t\t\t\t\t\tonIndexChange?.(pos);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, onIndexChange],\n\t);\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(false);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t(e.target as HTMLElement).releasePointerCapture(e.pointerId);\n\t\t\t}\n\t\t},\n\t\t[mode],\n\t);\n\n\t// ── Position mode: mouse-position-based ──────────────────────\n\tconst handlePointerMove = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tif (mode !== \"position\") return;\n\n\t\t\tconst container = containerRef.current;\n\t\t\tif (!container || images.length === 0) return;\n\n\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\tconst x = e.clientX - rect.left;\n\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\tsetActiveIndex(pos);\n\t\t\t\tonIndexChange?.(pos);\n\t\t\t}\n\n\t\t\tif (debug) {\n\t\t\t\tconst y = e.clientY - rect.top;\n\t\t\t\tsetDebugInfo(\n\t\t\t\t\t`x:${Math.round(x)}, y:${Math.round(y)}, img:${pos + 1}/${images.length}, w:${Math.round(rect.width)}, h:${Math.round(rect.height)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, debug, onIndexChange],\n\t);\n\n\t// Reset index if images change\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: intentional trigger when images change\n\tuseEffect(() => {\n\t\tsetActiveIndex(0);\n\t\tactiveIndexRef.current = 0;\n\t}, [images]);\n\n\t// ── Keyboard navigation ─────────────────────────────────────\n\tconst handleKeyDown = useCallback(\n\t\t(e: React.KeyboardEvent<HTMLElement>) => {\n\t\t\tif (images.length <= 1) return;\n\n\t\t\tlet next: number | null = null;\n\n\t\t\tif (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t\t\t? 0\n\t\t\t\t\t\t: activeIndexRef.current + 1;\n\t\t\t} else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current - 1 < 0\n\t\t\t\t\t\t? images.length - 1\n\t\t\t\t\t\t: activeIndexRef.current - 1;\n\t\t\t} else if (e.key === \"Home\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = 0;\n\t\t\t} else if (e.key === \"End\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = images.length - 1;\n\t\t\t}\n\n\t\t\tif (next !== null && next !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = next;\n\t\t\t\tsetActiveIndex(next);\n\t\t\t\tonIndexChange?.(next);\n\t\t\t}\n\t\t},\n\t\t[images.length, onIndexChange],\n\t);\n\n\t// ── Render ───────────────────────────────────────────────────\n\tif (images.length === 0) return null;\n\n\tconst activeImage = images[activeIndex];\n\tconst hasLink = !!activeImage.href;\n\n\tconst containerStyle: CSSProperties = {\n\t\tposition: \"relative\",\n\t\twidth: width ?? \"100%\",\n\t\theight: height ?? \"100%\",\n\t\toverflow: \"hidden\",\n\t\tcursor:\n\t\t\tisPointerDown || !(mode === \"position\" && showCursor)\n\t\t\t\t? hasLink\n\t\t\t\t\t? \"pointer\"\n\t\t\t\t\t: \"default\"\n\t\t\t\t: \"ew-resize\",\n\t\ttouchAction: mode === \"position\" ? \"none\" : undefined,\n\t\t...style,\n\t};\n\n\tconst imgElement = (\n\t\t<img\n\t\t\tsrc={activeImage.src}\n\t\t\talt={activeImage.alt ?? \"\"}\n\t\t\tclassName=\"cj-flip-previewer__img\"\n\t\t\tstyle={{ objectFit: fit }}\n\t\t\tdraggable={false}\n\t\t/>\n\t);\n\n\tconst content = hasLink ? (\n\t\t<a\n\t\t\thref={activeImage.href}\n\t\t\ttitle={activeImage.title}\n\t\t\ttarget={activeImage.target}\n\t\t\trel={activeImage.rel}\n\t\t\tclassName=\"cj-flip-previewer__link\"\n\t\t>\n\t\t\t{imgElement}\n\t\t</a>\n\t) : (\n\t\timgElement\n\t);\n\n\tconst progressPct =\n\t\timages.length > 0 ? (loadedCount / images.length) * 100 : 0;\n\n\treturn (\n\t\t<section\n\t\t\tref={containerRef}\n\t\t\taria-roledescription=\"image flipper\"\n\t\t\taria-label={`Image ${activeIndex + 1} of ${images.length}`}\n\t\t\ttabIndex={0}\n\t\t\tclassName={`cj-flip-previewer${className ? ` ${className}` : \"\"}`}\n\t\t\tstyle={containerStyle}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tonPointerUp={handlePointerUp}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t>\n\t\t\t{content}\n\n\t\t\t<div\n\t\t\t\tclassName=\"cj-flip-previewer__sr-only\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t>\n\t\t\t\t{activeImage.alt\n\t\t\t\t\t? `Image ${activeIndex + 1} of ${images.length}: ${activeImage.alt}`\n\t\t\t\t\t: `Image ${activeIndex + 1} of ${images.length}`}\n\t\t\t</div>\n\n\t\t\t{mode === \"hover\" && showProgress && !allLoaded && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"cj-flip-previewer__progress\"\n\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\taria-valuenow={Math.round(progressPct)}\n\t\t\t\t\taria-valuemin={0}\n\t\t\t\t\taria-valuemax={100}\n\t\t\t\t\taria-label=\"Loading images\"\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"cj-flip-previewer__progress-bar\"\n\t\t\t\t\t\tstyle={{ width: `${progressPct}%` }}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{mode === \"position\" && debug && (\n\t\t\t\t<div className=\"cj-flip-previewer__debug\" aria-hidden=\"true\">\n\t\t\t\t\t{debugInfo}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</section>\n\t);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/FlipPreviewer.tsx"],"sourcesContent":["import {\n\ttype CSSProperties,\n\ttype Ref,\n\tuseCallback,\n\tuseEffect,\n\tuseImperativeHandle,\n\tuseRef,\n\tuseState,\n} from \"react\";\n\nexport interface FlipPreviewerImage {\n\t/** Image source URL */\n\tsrc: string;\n\t/** Alt text for the image */\n\talt?: string;\n\t/** Optional link URL - clicking the image navigates here */\n\thref?: string;\n\t/** Link title attribute */\n\ttitle?: string;\n\t/** Link target attribute (e.g., \"_blank\") */\n\ttarget?: string;\n\t/** Link rel attribute (e.g., \"noopener noreferrer\") */\n\trel?: string;\n}\n\nexport interface FlipPreviewerRef {\n\t/** Start the hover animation (only applies in \"hover\" mode) */\n\tstart: () => void;\n\t/** Pause the animation and reset to the first frame (only applies in \"hover\" mode) */\n\tpause: () => void;\n}\n\nexport interface FlipPreviewerProps {\n\t/**\n\t * How images cycle:\n\t * - `\"position\"` - image changes based on horizontal mouse/touch position (default)\n\t * - `\"hover\"` - images auto-cycle on a timer when hovered\n\t */\n\tmode?: \"position\" | \"hover\";\n\t/** Array of images to flip through */\n\timages: FlipPreviewerImage[];\n\t/** Width of the component (CSS value). Omit to fill parent at 100%. */\n\twidth?: number | string;\n\t/** Height of the component (CSS value). Omit to fill parent at 100%. */\n\theight?: number | string;\n\t/**\n\t * How images fit within the container:\n\t * - `\"cover\"` - image covers the entire container, may crop (default)\n\t * - `\"contain\"` - image fits entirely within the container, may letterbox\n\t */\n\tfit?: \"cover\" | \"contain\";\n\t/** Delay in ms between frame transitions - only used in \"hover\" mode. Omit for max speed (requestAnimationFrame). */\n\tdelay?: number;\n\t/** Start animating automatically without hover - only used in \"hover\" mode (default: false) */\n\tautoPlay?: boolean;\n\t/** Show a progress bar while images preload - only used in \"hover\" mode (default: true) */\n\tshowProgress?: boolean;\n\t/** Show a horizontal resize cursor when in \"position\" mode (default: true) */\n\tshowCursor?: boolean;\n\t/** Show debug overlay with mouse coordinates and position info - only used in \"position\" mode */\n\tdebug?: boolean;\n\t/** Additional CSS class name(s) for the container */\n\tclassName?: string;\n\t/** Additional inline styles for the container */\n\tstyle?: CSSProperties;\n\t/** Callback fired when the active image index changes */\n\tonIndexChange?: (index: number) => void;\n\t/** Callback fired when all images have finished preloading - only used in \"hover\" mode */\n\tonImagesLoaded?: () => void;\n\t/** Imperative handle ref for start/pause control in \"hover\" mode */\n\tref?: Ref<FlipPreviewerRef>;\n}\n\nexport function FlipPreviewer({\n\tmode = \"position\",\n\timages,\n\twidth,\n\theight,\n\tfit = \"cover\",\n\tdelay,\n\tautoPlay = false,\n\tshowProgress = true,\n\tshowCursor = true,\n\tdebug = false,\n\tclassName,\n\tstyle,\n\tonIndexChange,\n\tonImagesLoaded,\n\tref,\n}: FlipPreviewerProps) {\n\tconst containerRef = useRef<HTMLElement>(null);\n\tconst timerRef = useRef<ReturnType<typeof setTimeout> | number | null>(null);\n\n\tconst [activeIndex, setActiveIndex] = useState(0);\n\tconst [debugInfo, setDebugInfo] = useState(\"\");\n\tconst [loadedCount, setLoadedCount] = useState(0);\n\tconst [allLoaded, setAllLoaded] = useState(false);\n\tconst [isPointerDown, setIsPointerDown] = useState(false);\n\n\tconst activeIndexRef = useRef(0);\n\tconst isPlayingRef = useRef(false);\n\n\t// ── Preload images (hover mode) ──────────────────────────────\n\tuseEffect(() => {\n\t\tif (mode !== \"hover\" || images.length === 0) {\n\t\t\tsetAllLoaded(true);\n\t\t\treturn;\n\t\t}\n\n\t\tlet loaded = 0;\n\t\tsetLoadedCount(0);\n\t\tsetAllLoaded(false);\n\n\t\tconst total = images.length;\n\t\timages.forEach(({ src }) => {\n\t\t\tconst img = new Image();\n\t\t\timg.onload = img.onerror = () => {\n\t\t\t\tloaded++;\n\t\t\t\tsetLoadedCount(loaded);\n\t\t\t\tif (loaded >= total) {\n\t\t\t\t\tsetAllLoaded(true);\n\t\t\t\t\tonImagesLoaded?.();\n\t\t\t\t}\n\t\t\t};\n\t\t\timg.src = src;\n\t\t});\n\t}, [mode, images, onImagesLoaded]);\n\n\t// ── Hover mode: animation ────────────────────────────────────\n\tconst clearTimer = useCallback(() => {\n\t\tif (timerRef.current !== null) {\n\t\t\tif (delay != null) {\n\t\t\t\tclearTimeout(timerRef.current as ReturnType<typeof setTimeout>);\n\t\t\t} else {\n\t\t\t\tcancelAnimationFrame(timerRef.current as number);\n\t\t\t}\n\t\t\ttimerRef.current = null;\n\t\t}\n\t}, [delay]);\n\n\tconst advanceFrame = useCallback(() => {\n\t\tif (!isPlayingRef.current) return;\n\t\tif (images.length <= 1) return;\n\n\t\tconst next =\n\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t? 0\n\t\t\t\t: activeIndexRef.current + 1;\n\n\t\tactiveIndexRef.current = next;\n\t\tsetActiveIndex(next);\n\t\tonIndexChange?.(next);\n\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [images.length, delay, onIndexChange]);\n\n\tconst startAnimation = useCallback(() => {\n\t\tif (!allLoaded || images.length <= 1) return;\n\t\tisPlayingRef.current = true;\n\t\tclearTimer();\n\t\tif (delay != null) {\n\t\t\ttimerRef.current = setTimeout(advanceFrame, delay);\n\t\t} else {\n\t\t\ttimerRef.current = requestAnimationFrame(advanceFrame);\n\t\t}\n\t}, [allLoaded, images.length, delay, advanceFrame, clearTimer]);\n\n\tconst stopAnimation = useCallback(() => {\n\t\tisPlayingRef.current = false;\n\t\tclearTimer();\n\t\tactiveIndexRef.current = 0;\n\t\tsetActiveIndex(0);\n\t\tonIndexChange?.(0);\n\t}, [clearTimer, onIndexChange]);\n\n\t// Auto-play on mount if enabled (hover mode)\n\tuseEffect(() => {\n\t\tif (mode === \"hover\" && autoPlay && allLoaded) {\n\t\t\tstartAnimation();\n\t\t}\n\t\treturn () => clearTimer();\n\t}, [mode, autoPlay, allLoaded, startAnimation, clearTimer]);\n\n\t// Expose start/pause via ref (hover mode)\n\tuseImperativeHandle(\n\t\tref,\n\t\t() => ({\n\t\t\tstart: startAnimation,\n\t\t\tpause: stopAnimation,\n\t\t}),\n\t\t[startAnimation, stopAnimation],\n\t);\n\n\tconst handleMouseEnter = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstartAnimation();\n\t\t}\n\t}, [mode, autoPlay, startAnimation]);\n\n\tconst handleMouseLeave = useCallback(() => {\n\t\tif (mode === \"hover\" && !autoPlay) {\n\t\t\tstopAnimation();\n\t\t}\n\t\tsetIsPointerDown(false);\n\t}, [mode, autoPlay, stopAnimation]);\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(true);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t// Capture pointer so move events keep firing on touch devices\n\t\t\t\t(e.target as HTMLElement).setPointerCapture(e.pointerId);\n\n\t\t\t\t// Update index immediately on press\n\t\t\t\tconst container = containerRef.current;\n\t\t\t\tif (container && images.length > 0) {\n\t\t\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\t\t\tconst x = e.clientX - rect.left;\n\t\t\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\t\t\tsetActiveIndex(pos);\n\t\t\t\t\t\tonIndexChange?.(pos);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, onIndexChange],\n\t);\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tsetIsPointerDown(false);\n\n\t\t\tif (mode === \"position\") {\n\t\t\t\t(e.target as HTMLElement).releasePointerCapture(e.pointerId);\n\t\t\t}\n\t\t},\n\t\t[mode],\n\t);\n\n\t// ── Position mode: mouse-position-based ──────────────────────\n\tconst handlePointerMove = useCallback(\n\t\t(e: React.PointerEvent<HTMLElement>) => {\n\t\t\tif (mode !== \"position\") return;\n\n\t\t\tconst container = containerRef.current;\n\t\t\tif (!container || images.length === 0) return;\n\n\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\tconst x = e.clientX - rect.left;\n\t\t\tlet pos = Math.floor((x / rect.width) * images.length);\n\t\t\tpos = Math.max(0, Math.min(pos, images.length - 1));\n\n\t\t\tif (pos !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = pos;\n\t\t\t\tsetActiveIndex(pos);\n\t\t\t\tonIndexChange?.(pos);\n\t\t\t}\n\n\t\t\tif (debug) {\n\t\t\t\tconst y = e.clientY - rect.top;\n\t\t\t\tsetDebugInfo(\n\t\t\t\t\t`x:${Math.round(x)}, y:${Math.round(y)}, img:${pos + 1}/${images.length}, w:${Math.round(rect.width)}, h:${Math.round(rect.height)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\t[mode, images.length, debug, onIndexChange],\n\t);\n\n\t// Reset index if images change\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: intentional trigger when images change\n\tuseEffect(() => {\n\t\tsetActiveIndex(0);\n\t\tactiveIndexRef.current = 0;\n\t}, [images]);\n\n\t// ── Keyboard navigation ─────────────────────────────────────\n\tconst handleKeyDown = useCallback(\n\t\t(e: React.KeyboardEvent<HTMLElement>) => {\n\t\t\tif (images.length <= 1) return;\n\n\t\t\tlet next: number | null = null;\n\n\t\t\tif (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current + 1 >= images.length\n\t\t\t\t\t\t? 0\n\t\t\t\t\t\t: activeIndexRef.current + 1;\n\t\t\t} else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext =\n\t\t\t\t\tactiveIndexRef.current - 1 < 0\n\t\t\t\t\t\t? images.length - 1\n\t\t\t\t\t\t: activeIndexRef.current - 1;\n\t\t\t} else if (e.key === \"Home\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = 0;\n\t\t\t} else if (e.key === \"End\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tnext = images.length - 1;\n\t\t\t}\n\n\t\t\tif (next !== null && next !== activeIndexRef.current) {\n\t\t\t\tactiveIndexRef.current = next;\n\t\t\t\tsetActiveIndex(next);\n\t\t\t\tonIndexChange?.(next);\n\t\t\t}\n\t\t},\n\t\t[images.length, onIndexChange],\n\t);\n\n\t// ── Render ───────────────────────────────────────────────────\n\tif (images.length === 0) return null;\n\n\tconst activeImage = images[activeIndex];\n\tconst hasLink = !!activeImage.href;\n\n\tconst containerStyle: CSSProperties = {\n\t\tposition: \"relative\",\n\t\twidth: width ?? \"100%\",\n\t\theight: height ?? \"100%\",\n\t\toverflow: \"hidden\",\n\t\tcursor:\n\t\t\tisPointerDown || !(mode === \"position\" && showCursor)\n\t\t\t\t? hasLink\n\t\t\t\t\t? \"pointer\"\n\t\t\t\t\t: \"default\"\n\t\t\t\t: \"ew-resize\",\n\t\ttouchAction: mode === \"position\" ? \"none\" : undefined,\n\t\t...style,\n\t};\n\n\tconst imgElement = (\n\t\t<img\n\t\t\tsrc={activeImage.src}\n\t\t\talt={activeImage.alt ?? \"\"}\n\t\t\tclassName=\"cj-flip-previewer__img\"\n\t\t\tstyle={{ objectFit: fit }}\n\t\t\tdraggable={false}\n\t\t/>\n\t);\n\n\tconst content = hasLink ? (\n\t\t<a\n\t\t\thref={activeImage.href}\n\t\t\ttitle={activeImage.title}\n\t\t\ttarget={activeImage.target}\n\t\t\trel={activeImage.rel}\n\t\t\tclassName=\"cj-flip-previewer__link\"\n\t\t>\n\t\t\t{imgElement}\n\t\t</a>\n\t) : (\n\t\timgElement\n\t);\n\n\tconst progressPct =\n\t\timages.length > 0 ? (loadedCount / images.length) * 100 : 0;\n\n\treturn (\n\t\t<section\n\t\t\tref={containerRef}\n\t\t\taria-roledescription=\"image flipper\"\n\t\t\taria-label={`Image ${activeIndex + 1} of ${images.length}`}\n\t\t\ttabIndex={0}\n\t\t\tclassName={`cj-flip-previewer${className ? ` ${className}` : \"\"}`}\n\t\t\tstyle={containerStyle}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tonPointerUp={handlePointerUp}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t>\n\t\t\t{content}\n\n\t\t\t<div\n\t\t\t\tclassName=\"cj-flip-previewer__sr-only\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t>\n\t\t\t\t{activeImage.alt\n\t\t\t\t\t? `Image ${activeIndex + 1} of ${images.length}: ${activeImage.alt}`\n\t\t\t\t\t: `Image ${activeIndex + 1} of ${images.length}`}\n\t\t\t</div>\n\n\t\t\t{mode === \"hover\" && showProgress && !allLoaded && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"cj-flip-previewer__progress\"\n\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\taria-valuenow={Math.round(progressPct)}\n\t\t\t\t\taria-valuemin={0}\n\t\t\t\t\taria-valuemax={100}\n\t\t\t\t\taria-label=\"Loading images\"\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"cj-flip-previewer__progress-bar\"\n\t\t\t\t\t\tstyle={{ width: `${progressPct}%` }}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{mode === \"position\" && debug && (\n\t\t\t\t<div className=\"cj-flip-previewer__debug\" aria-hidden=\"true\">\n\t\t\t\t\t{debugInfo}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</section>\n\t);\n}\n"],"mappings":"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","names":[]}
package/dist/server.d.cts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { FlipPreviewerImage } from './index.cjs';
2
- import 'react/jsx-runtime';
3
2
  import 'react';
4
3
 
5
4
  /**
package/dist/server.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { FlipPreviewerImage } from './index.js';
2
- import 'react/jsx-runtime';
3
2
  import 'react';
4
3
 
5
4
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cjboco/cj-image-flip-previewer",
3
- "version": "1.1.1",
4
- "description": "React components for interactive image previews flip through images by mouse position (FlipPreviewer) or animate frames on hover like a video previewer (VideoPreviewer).",
3
+ "version": "1.1.3",
4
+ "description": "React components for interactive image previews - flip through images by mouse position (FlipPreviewer) or animate frames on hover like a video previewer (VideoPreviewer).",
5
5
  "author": "Doug Jones",
6
6
  "license": "BSD-3-Clause",
7
7
  "repository": {
@@ -68,12 +68,12 @@
68
68
  "react-dom": ">=19.0.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@types/node": "^25.9.1",
72
- "@types/react": "^19.0.0",
73
- "@types/react-dom": "^19.0.0",
74
- "react": "^19.0.0",
75
- "react-dom": "^19.0.0",
76
- "tsup": "^8.0.0",
77
- "typescript": "^5.7.0"
71
+ "@types/node": "^25.9.4",
72
+ "@types/react": "^19.2.17",
73
+ "@types/react-dom": "^19.2.3",
74
+ "react": "^19.2.7",
75
+ "react-dom": "^19.2.7",
76
+ "tsup": "^8.5.1",
77
+ "typescript": "^5.9.3"
78
78
  }
79
79
  }