@cjboco/cj-image-flip-previewer 1.0.1 → 1.1.1
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/dist/index.cjs +106 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +106 -25
- package/dist/index.js.map +1 -1
- package/dist/styles.css +15 -0
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ function FlipPreviewer({
|
|
|
50
50
|
const [debugInfo, setDebugInfo] = (0, import_react.useState)("");
|
|
51
51
|
const [loadedCount, setLoadedCount] = (0, import_react.useState)(0);
|
|
52
52
|
const [allLoaded, setAllLoaded] = (0, import_react.useState)(false);
|
|
53
|
+
const [isPointerDown, setIsPointerDown] = (0, import_react.useState)(false);
|
|
53
54
|
const activeIndexRef = (0, import_react.useRef)(0);
|
|
54
55
|
const isPlayingRef = (0, import_react.useRef)(false);
|
|
55
56
|
(0, import_react.useEffect)(() => {
|
|
@@ -137,7 +138,38 @@ function FlipPreviewer({
|
|
|
137
138
|
if (mode === "hover" && !autoPlay) {
|
|
138
139
|
stopAnimation();
|
|
139
140
|
}
|
|
141
|
+
setIsPointerDown(false);
|
|
140
142
|
}, [mode, autoPlay, stopAnimation]);
|
|
143
|
+
const handlePointerDown = (0, import_react.useCallback)(
|
|
144
|
+
(e) => {
|
|
145
|
+
setIsPointerDown(true);
|
|
146
|
+
if (mode === "position") {
|
|
147
|
+
e.target.setPointerCapture(e.pointerId);
|
|
148
|
+
const container = containerRef.current;
|
|
149
|
+
if (container && images.length > 0) {
|
|
150
|
+
const rect = container.getBoundingClientRect();
|
|
151
|
+
const x = e.clientX - rect.left;
|
|
152
|
+
let pos = Math.floor(x / rect.width * images.length);
|
|
153
|
+
pos = Math.max(0, Math.min(pos, images.length - 1));
|
|
154
|
+
if (pos !== activeIndexRef.current) {
|
|
155
|
+
activeIndexRef.current = pos;
|
|
156
|
+
setActiveIndex(pos);
|
|
157
|
+
onIndexChange?.(pos);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[mode, images.length, onIndexChange]
|
|
163
|
+
);
|
|
164
|
+
const handlePointerUp = (0, import_react.useCallback)(
|
|
165
|
+
(e) => {
|
|
166
|
+
setIsPointerDown(false);
|
|
167
|
+
if (mode === "position") {
|
|
168
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
[mode]
|
|
172
|
+
);
|
|
141
173
|
const handlePointerMove = (0, import_react.useCallback)(
|
|
142
174
|
(e) => {
|
|
143
175
|
if (mode !== "position") return;
|
|
@@ -165,6 +197,31 @@ function FlipPreviewer({
|
|
|
165
197
|
setActiveIndex(0);
|
|
166
198
|
activeIndexRef.current = 0;
|
|
167
199
|
}, [images]);
|
|
200
|
+
const handleKeyDown = (0, import_react.useCallback)(
|
|
201
|
+
(e) => {
|
|
202
|
+
if (images.length <= 1) return;
|
|
203
|
+
let next = null;
|
|
204
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
next = activeIndexRef.current + 1 >= images.length ? 0 : activeIndexRef.current + 1;
|
|
207
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
next = activeIndexRef.current - 1 < 0 ? images.length - 1 : activeIndexRef.current - 1;
|
|
210
|
+
} else if (e.key === "Home") {
|
|
211
|
+
e.preventDefault();
|
|
212
|
+
next = 0;
|
|
213
|
+
} else if (e.key === "End") {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
next = images.length - 1;
|
|
216
|
+
}
|
|
217
|
+
if (next !== null && next !== activeIndexRef.current) {
|
|
218
|
+
activeIndexRef.current = next;
|
|
219
|
+
setActiveIndex(next);
|
|
220
|
+
onIndexChange?.(next);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
[images.length, onIndexChange]
|
|
224
|
+
);
|
|
168
225
|
if (images.length === 0) return null;
|
|
169
226
|
const activeImage = images[activeIndex];
|
|
170
227
|
const hasLink = !!activeImage.href;
|
|
@@ -173,7 +230,8 @@ function FlipPreviewer({
|
|
|
173
230
|
width: width ?? "100%",
|
|
174
231
|
height: height ?? "100%",
|
|
175
232
|
overflow: "hidden",
|
|
176
|
-
cursor:
|
|
233
|
+
cursor: isPointerDown || !(mode === "position" && showCursor) ? hasLink ? "pointer" : "default" : "ew-resize",
|
|
234
|
+
touchAction: mode === "position" ? "none" : void 0,
|
|
177
235
|
...style
|
|
178
236
|
};
|
|
179
237
|
const imgElement = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -198,30 +256,53 @@ function FlipPreviewer({
|
|
|
198
256
|
}
|
|
199
257
|
) : imgElement;
|
|
200
258
|
const progressPct = images.length > 0 ? loadedCount / images.length * 100 : 0;
|
|
201
|
-
return (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
259
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
260
|
+
"section",
|
|
261
|
+
{
|
|
262
|
+
ref: containerRef,
|
|
263
|
+
"aria-roledescription": "image flipper",
|
|
264
|
+
"aria-label": `Image ${activeIndex + 1} of ${images.length}`,
|
|
265
|
+
tabIndex: 0,
|
|
266
|
+
className: `cj-flip-previewer${className ? ` ${className}` : ""}`,
|
|
267
|
+
style: containerStyle,
|
|
268
|
+
onPointerMove: handlePointerMove,
|
|
269
|
+
onPointerDown: handlePointerDown,
|
|
270
|
+
onPointerUp: handlePointerUp,
|
|
271
|
+
onMouseEnter: handleMouseEnter,
|
|
272
|
+
onMouseLeave: handleMouseLeave,
|
|
273
|
+
onKeyDown: handleKeyDown,
|
|
274
|
+
children: [
|
|
275
|
+
content,
|
|
276
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
277
|
+
"div",
|
|
278
|
+
{
|
|
279
|
+
className: "cj-flip-previewer__sr-only",
|
|
280
|
+
"aria-live": "polite",
|
|
281
|
+
"aria-atomic": "true",
|
|
282
|
+
children: activeImage.alt ? `Image ${activeIndex + 1} of ${images.length}: ${activeImage.alt}` : `Image ${activeIndex + 1} of ${images.length}`
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
mode === "hover" && showProgress && !allLoaded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
286
|
+
"div",
|
|
287
|
+
{
|
|
288
|
+
className: "cj-flip-previewer__progress",
|
|
289
|
+
role: "progressbar",
|
|
290
|
+
"aria-valuenow": Math.round(progressPct),
|
|
291
|
+
"aria-valuemin": 0,
|
|
292
|
+
"aria-valuemax": 100,
|
|
293
|
+
"aria-label": "Loading images",
|
|
294
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
295
|
+
"div",
|
|
296
|
+
{
|
|
297
|
+
className: "cj-flip-previewer__progress-bar",
|
|
298
|
+
style: { width: `${progressPct}%` }
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
),
|
|
303
|
+
mode === "position" && debug && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cj-flip-previewer__debug", "aria-hidden": "true", children: debugInfo })
|
|
304
|
+
]
|
|
305
|
+
}
|
|
225
306
|
);
|
|
226
307
|
}
|
|
227
308
|
// Annotate the CommonJS export names for ESM import in node:
|
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<HTMLDivElement>(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\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}, [mode, autoPlay, stopAnimation]);\n\n\t// ── Position mode: mouse-position-based ──────────────────────\n\tconst handlePointerMove = useCallback(\n\t\t(e: React.PointerEvent<HTMLDivElement>) => {\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// ── 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: hasLink\n\t\t\t? \"pointer\"\n\t\t\t: mode === \"position\" && showCursor\n\t\t\t\t? \"ew-resize\"\n\t\t\t\t: \"default\",\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// biome-ignore lint/a11y/noStaticElementInteractions: container tracks pointer position for image flipping\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tclassName={`cj-flip-previewer${className ? ` ${className}` : \"\"}`}\n\t\t\tstyle={containerStyle}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\n\t\t>\n\t\t\t{content}\n\n\t\t\t{mode === \"hover\" && showProgress && !allLoaded && (\n\t\t\t\t<div className=\"cj-flip-previewer__progress\">\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\">{debugInfo}</div>\n\t\t\t)}\n\t\t</div>\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.js
CHANGED
|
@@ -30,6 +30,7 @@ function FlipPreviewer({
|
|
|
30
30
|
const [debugInfo, setDebugInfo] = useState("");
|
|
31
31
|
const [loadedCount, setLoadedCount] = useState(0);
|
|
32
32
|
const [allLoaded, setAllLoaded] = useState(false);
|
|
33
|
+
const [isPointerDown, setIsPointerDown] = useState(false);
|
|
33
34
|
const activeIndexRef = useRef(0);
|
|
34
35
|
const isPlayingRef = useRef(false);
|
|
35
36
|
useEffect(() => {
|
|
@@ -117,7 +118,38 @@ function FlipPreviewer({
|
|
|
117
118
|
if (mode === "hover" && !autoPlay) {
|
|
118
119
|
stopAnimation();
|
|
119
120
|
}
|
|
121
|
+
setIsPointerDown(false);
|
|
120
122
|
}, [mode, autoPlay, stopAnimation]);
|
|
123
|
+
const handlePointerDown = useCallback(
|
|
124
|
+
(e) => {
|
|
125
|
+
setIsPointerDown(true);
|
|
126
|
+
if (mode === "position") {
|
|
127
|
+
e.target.setPointerCapture(e.pointerId);
|
|
128
|
+
const container = containerRef.current;
|
|
129
|
+
if (container && images.length > 0) {
|
|
130
|
+
const rect = container.getBoundingClientRect();
|
|
131
|
+
const x = e.clientX - rect.left;
|
|
132
|
+
let pos = Math.floor(x / rect.width * images.length);
|
|
133
|
+
pos = Math.max(0, Math.min(pos, images.length - 1));
|
|
134
|
+
if (pos !== activeIndexRef.current) {
|
|
135
|
+
activeIndexRef.current = pos;
|
|
136
|
+
setActiveIndex(pos);
|
|
137
|
+
onIndexChange?.(pos);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
[mode, images.length, onIndexChange]
|
|
143
|
+
);
|
|
144
|
+
const handlePointerUp = useCallback(
|
|
145
|
+
(e) => {
|
|
146
|
+
setIsPointerDown(false);
|
|
147
|
+
if (mode === "position") {
|
|
148
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[mode]
|
|
152
|
+
);
|
|
121
153
|
const handlePointerMove = useCallback(
|
|
122
154
|
(e) => {
|
|
123
155
|
if (mode !== "position") return;
|
|
@@ -145,6 +177,31 @@ function FlipPreviewer({
|
|
|
145
177
|
setActiveIndex(0);
|
|
146
178
|
activeIndexRef.current = 0;
|
|
147
179
|
}, [images]);
|
|
180
|
+
const handleKeyDown = useCallback(
|
|
181
|
+
(e) => {
|
|
182
|
+
if (images.length <= 1) return;
|
|
183
|
+
let next = null;
|
|
184
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
next = activeIndexRef.current + 1 >= images.length ? 0 : activeIndexRef.current + 1;
|
|
187
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
next = activeIndexRef.current - 1 < 0 ? images.length - 1 : activeIndexRef.current - 1;
|
|
190
|
+
} else if (e.key === "Home") {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
next = 0;
|
|
193
|
+
} else if (e.key === "End") {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
next = images.length - 1;
|
|
196
|
+
}
|
|
197
|
+
if (next !== null && next !== activeIndexRef.current) {
|
|
198
|
+
activeIndexRef.current = next;
|
|
199
|
+
setActiveIndex(next);
|
|
200
|
+
onIndexChange?.(next);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
[images.length, onIndexChange]
|
|
204
|
+
);
|
|
148
205
|
if (images.length === 0) return null;
|
|
149
206
|
const activeImage = images[activeIndex];
|
|
150
207
|
const hasLink = !!activeImage.href;
|
|
@@ -153,7 +210,8 @@ function FlipPreviewer({
|
|
|
153
210
|
width: width ?? "100%",
|
|
154
211
|
height: height ?? "100%",
|
|
155
212
|
overflow: "hidden",
|
|
156
|
-
cursor:
|
|
213
|
+
cursor: isPointerDown || !(mode === "position" && showCursor) ? hasLink ? "pointer" : "default" : "ew-resize",
|
|
214
|
+
touchAction: mode === "position" ? "none" : void 0,
|
|
157
215
|
...style
|
|
158
216
|
};
|
|
159
217
|
const imgElement = /* @__PURE__ */ jsx(
|
|
@@ -178,30 +236,53 @@ function FlipPreviewer({
|
|
|
178
236
|
}
|
|
179
237
|
) : imgElement;
|
|
180
238
|
const progressPct = images.length > 0 ? loadedCount / images.length * 100 : 0;
|
|
181
|
-
return (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
239
|
+
return /* @__PURE__ */ jsxs(
|
|
240
|
+
"section",
|
|
241
|
+
{
|
|
242
|
+
ref: containerRef,
|
|
243
|
+
"aria-roledescription": "image flipper",
|
|
244
|
+
"aria-label": `Image ${activeIndex + 1} of ${images.length}`,
|
|
245
|
+
tabIndex: 0,
|
|
246
|
+
className: `cj-flip-previewer${className ? ` ${className}` : ""}`,
|
|
247
|
+
style: containerStyle,
|
|
248
|
+
onPointerMove: handlePointerMove,
|
|
249
|
+
onPointerDown: handlePointerDown,
|
|
250
|
+
onPointerUp: handlePointerUp,
|
|
251
|
+
onMouseEnter: handleMouseEnter,
|
|
252
|
+
onMouseLeave: handleMouseLeave,
|
|
253
|
+
onKeyDown: handleKeyDown,
|
|
254
|
+
children: [
|
|
255
|
+
content,
|
|
256
|
+
/* @__PURE__ */ jsx(
|
|
257
|
+
"div",
|
|
258
|
+
{
|
|
259
|
+
className: "cj-flip-previewer__sr-only",
|
|
260
|
+
"aria-live": "polite",
|
|
261
|
+
"aria-atomic": "true",
|
|
262
|
+
children: activeImage.alt ? `Image ${activeIndex + 1} of ${images.length}: ${activeImage.alt}` : `Image ${activeIndex + 1} of ${images.length}`
|
|
263
|
+
}
|
|
264
|
+
),
|
|
265
|
+
mode === "hover" && showProgress && !allLoaded && /* @__PURE__ */ jsx(
|
|
266
|
+
"div",
|
|
267
|
+
{
|
|
268
|
+
className: "cj-flip-previewer__progress",
|
|
269
|
+
role: "progressbar",
|
|
270
|
+
"aria-valuenow": Math.round(progressPct),
|
|
271
|
+
"aria-valuemin": 0,
|
|
272
|
+
"aria-valuemax": 100,
|
|
273
|
+
"aria-label": "Loading images",
|
|
274
|
+
children: /* @__PURE__ */ jsx(
|
|
275
|
+
"div",
|
|
276
|
+
{
|
|
277
|
+
className: "cj-flip-previewer__progress-bar",
|
|
278
|
+
style: { width: `${progressPct}%` }
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
),
|
|
283
|
+
mode === "position" && debug && /* @__PURE__ */ jsx("div", { className: "cj-flip-previewer__debug", "aria-hidden": "true", children: debugInfo })
|
|
284
|
+
]
|
|
285
|
+
}
|
|
205
286
|
);
|
|
206
287
|
}
|
|
207
288
|
export {
|
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<HTMLDivElement>(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\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}, [mode, autoPlay, stopAnimation]);\n\n\t// ── Position mode: mouse-position-based ──────────────────────\n\tconst handlePointerMove = useCallback(\n\t\t(e: React.PointerEvent<HTMLDivElement>) => {\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// ── 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: hasLink\n\t\t\t? \"pointer\"\n\t\t\t: mode === \"position\" && showCursor\n\t\t\t\t? \"ew-resize\"\n\t\t\t\t: \"default\",\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// biome-ignore lint/a11y/noStaticElementInteractions: container tracks pointer position for image flipping\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tclassName={`cj-flip-previewer${className ? ` ${className}` : \"\"}`}\n\t\t\tstyle={containerStyle}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\tonMouseLeave={handleMouseLeave}\n\t\t>\n\t\t\t{content}\n\n\t\t\t{mode === \"hover\" && showProgress && !allLoaded && (\n\t\t\t\t<div className=\"cj-flip-previewer__progress\">\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\">{debugInfo}</div>\n\t\t\t)}\n\t\t</div>\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/styles.css
CHANGED
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
user-select: none;
|
|
6
6
|
touch-action: none;
|
|
7
7
|
}
|
|
8
|
+
.cj-flip-previewer:focus-visible {
|
|
9
|
+
outline: var(--cj-flip-focus-outline, 2px solid #005fcc);
|
|
10
|
+
outline-offset: var(--cj-flip-focus-outline-offset, 2px);
|
|
11
|
+
}
|
|
12
|
+
.cj-flip-previewer__sr-only {
|
|
13
|
+
position: absolute;
|
|
14
|
+
width: 1px;
|
|
15
|
+
height: 1px;
|
|
16
|
+
padding: 0;
|
|
17
|
+
margin: -1px;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
clip: rect(0, 0, 0, 0);
|
|
20
|
+
white-space: nowrap;
|
|
21
|
+
border: 0;
|
|
22
|
+
}
|
|
8
23
|
.cj-flip-previewer__img {
|
|
9
24
|
display: block;
|
|
10
25
|
width: 100%;
|
package/dist/styles.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["@layer components {\n .cj-flip-previewer {\n display: inline-block;\n user-select: none;\n touch-action: none;\n }\n\n .cj-flip-previewer__img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n }\n\n .cj-flip-previewer__link {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .cj-flip-previewer__debug {\n position: absolute;\n top: 2px;\n left: 2px;\n font-family: monospace;\n font-size: var(--cj-flip-debug-font-size, 11px);\n background: var(--cj-flip-debug-bg, rgba(0, 0, 0, 0.6));\n color: var(--cj-flip-debug-color, #fff);\n padding: 2px 6px;\n border-radius: 3px;\n pointer-events: none;\n z-index: 10;\n }\n\n .cj-flip-previewer__progress {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: var(--cj-flip-progress-height, 4px);\n background: var(--cj-flip-progress-bg, rgba(0, 0, 0, 0.3));\n z-index: 20;\n overflow: hidden;\n }\n\n .cj-flip-previewer__progress-bar {\n height: 100%;\n background: var(--cj-flip-progress-color, #6bc4f7);\n transition: width var(--cj-flip-progress-speed, 0.2s) ease;\n }\n}\n"],"mappings":";AAAA;AACE,GAAC;AACC,aAAS;AACT,iBAAa;AACb,kBAAc;AAChB;AAEA,GAAC;AACC,aAAS;AACT,WAAO;AACP,YAAQ;AACR,gBAAY;AACZ,oBAAgB;AAClB;AAEA,GAAC;AACC,aAAS;AACT,WAAO;AACP,YAAQ;AACV;AAEA,GAAC;AACC,cAAU;AACV,SAAK;AACL,UAAM;AACN,iBAAa;AACb,eAAW,IAAI,yBAAyB,EAAE;AAC1C,gBAAY,IAAI,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClD,WAAO,IAAI,qBAAqB,EAAE;AAClC,aAAS,IAAI;AACb,mBAAe;AACf,oBAAgB;AAChB,aAAS;AACX;AAEA,GAAC;AACC,cAAU;AACV,SAAK;AACL,UAAM;AACN,WAAO;AACP,YAAQ,IAAI,yBAAyB,EAAE;AACvC,gBAAY,IAAI,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrD,aAAS;AACT,cAAU;AACZ;AAEA,GAAC;AACC,YAAQ;AACR,gBAAY,IAAI,wBAAwB,EAAE;AAC1C,gBAAY,MAAM,IAAI,wBAAwB,EAAE,MAAM;AACxD;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["@layer components {\n .cj-flip-previewer {\n display: inline-block;\n user-select: none;\n touch-action: none;\n }\n\n .cj-flip-previewer:focus-visible {\n outline: var(--cj-flip-focus-outline, 2px solid #005fcc);\n outline-offset: var(--cj-flip-focus-outline-offset, 2px);\n }\n\n .cj-flip-previewer__sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n .cj-flip-previewer__img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n }\n\n .cj-flip-previewer__link {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .cj-flip-previewer__debug {\n position: absolute;\n top: 2px;\n left: 2px;\n font-family: monospace;\n font-size: var(--cj-flip-debug-font-size, 11px);\n background: var(--cj-flip-debug-bg, rgba(0, 0, 0, 0.6));\n color: var(--cj-flip-debug-color, #fff);\n padding: 2px 6px;\n border-radius: 3px;\n pointer-events: none;\n z-index: 10;\n }\n\n .cj-flip-previewer__progress {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: var(--cj-flip-progress-height, 4px);\n background: var(--cj-flip-progress-bg, rgba(0, 0, 0, 0.3));\n z-index: 20;\n overflow: hidden;\n }\n\n .cj-flip-previewer__progress-bar {\n height: 100%;\n background: var(--cj-flip-progress-color, #6bc4f7);\n transition: width var(--cj-flip-progress-speed, 0.2s) ease;\n }\n}\n"],"mappings":";AAAA;AACE,GAAC;AACC,aAAS;AACT,iBAAa;AACb,kBAAc;AAChB;AAEA,GANC,iBAMiB;AAChB,aAAS,IAAI,uBAAuB,EAAE,IAAI,MAAM;AAChD,oBAAgB,IAAI,8BAA8B,EAAE;AACtD;AAEA,GAAC;AACC,cAAU;AACV,WAAO;AACP,YAAQ;AACR,aAAS;AACT,YAAQ;AACR,cAAU;AACV,UAAM,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpB,iBAAa;AACb,YAAQ;AACV;AAEA,GAAC;AACC,aAAS;AACT,WAAO;AACP,YAAQ;AACR,gBAAY;AACZ,oBAAgB;AAClB;AAEA,GAAC;AACC,aAAS;AACT,WAAO;AACP,YAAQ;AACV;AAEA,GAAC;AACC,cAAU;AACV,SAAK;AACL,UAAM;AACN,iBAAa;AACb,eAAW,IAAI,yBAAyB,EAAE;AAC1C,gBAAY,IAAI,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClD,WAAO,IAAI,qBAAqB,EAAE;AAClC,aAAS,IAAI;AACb,mBAAe;AACf,oBAAgB;AAChB,aAAS;AACX;AAEA,GAAC;AACC,cAAU;AACV,SAAK;AACL,UAAM;AACN,WAAO;AACP,YAAQ,IAAI,yBAAyB,EAAE;AACvC,gBAAY,IAAI,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrD,aAAS;AACT,cAAU;AACZ;AAEA,GAAC;AACC,YAAQ;AACR,gBAAY,IAAI,wBAAwB,EAAE;AAC1C,gBAAY,MAAM,IAAI,wBAAwB,EAAE,MAAM;AACxD;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjboco/cj-image-flip-previewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
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",
|