@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 +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -12
- package/dist/index.d.ts +12 -12
- package/dist/index.js.map +1 -1
- package/dist/server.d.cts +0 -1
- package/dist/server.d.ts +0 -1
- package/package.json +9 -9
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"`**
|
|
13
|
-
- **`mode="hover"`**
|
|
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` |
|
|
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` |
|
|
152
|
-
| `style` | `CSSProperties` |
|
|
153
|
-
| `onIndexChange` | `(index: number) => void` |
|
|
154
|
-
| `onImagesLoaded` | `() => void` |
|
|
155
|
-
| `ref` | `Ref<FlipPreviewerRef>` |
|
|
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
|
|
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
|
|
260
|
+
Doug Jones - [Creative Juices, Bo. Co.](https://www.cjboco.com)
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
|
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
|
|
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"`
|
|
28
|
-
* - `"hover"`
|
|
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"`
|
|
40
|
-
* - `"contain"`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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):
|
|
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
|
|
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
|
|
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"`
|
|
28
|
-
* - `"hover"`
|
|
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"`
|
|
40
|
-
* - `"contain"`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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):
|
|
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
package/dist/server.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjboco/cj-image-flip-previewer",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "React components for interactive image previews
|
|
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.
|
|
72
|
-
"@types/react": "^19.
|
|
73
|
-
"@types/react-dom": "^19.
|
|
74
|
-
"react": "^19.
|
|
75
|
-
"react-dom": "^19.
|
|
76
|
-
"tsup": "^8.
|
|
77
|
-
"typescript": "^5.
|
|
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
|
}
|