react-media-lightbox 0.1.0 → 0.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 +65 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +66 -9
- package/dist/index.js.map +1 -1
- package/dist/styles.css +103 -24
- package/package.json +9 -1
package/dist/index.cjs
CHANGED
|
@@ -69,6 +69,8 @@ function getEmbedURL(url, provider, autoplay) {
|
|
|
69
69
|
}
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
|
+
var SWIPE_THRESHOLD = 50;
|
|
73
|
+
var CLOSE_THRESHOLD = 110;
|
|
72
74
|
var CloseIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) });
|
|
73
75
|
var PrevIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) });
|
|
74
76
|
var NextIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }) });
|
|
@@ -145,6 +147,10 @@ function MediaLightbox({
|
|
|
145
147
|
}) {
|
|
146
148
|
const count = items?.length ?? 0;
|
|
147
149
|
const [currentIndex, setCurrentIndex] = react.useState(initialIndex);
|
|
150
|
+
const [drag, setDrag] = react.useState(null);
|
|
151
|
+
const touchRef = react.useRef(
|
|
152
|
+
null
|
|
153
|
+
);
|
|
148
154
|
const handlePrev = react.useCallback(() => {
|
|
149
155
|
setCurrentIndex((prev) => {
|
|
150
156
|
const next = prev === 0 ? loop ? count - 1 : 0 : prev - 1;
|
|
@@ -159,6 +165,39 @@ function MediaLightbox({
|
|
|
159
165
|
return next;
|
|
160
166
|
});
|
|
161
167
|
}, [count, loop, onIndexChange]);
|
|
168
|
+
const handleTouchStart = react.useCallback((e) => {
|
|
169
|
+
if (e.touches.length !== 1) return;
|
|
170
|
+
const t = e.touches[0];
|
|
171
|
+
touchRef.current = { x: t.clientX, y: t.clientY, axis: null };
|
|
172
|
+
}, []);
|
|
173
|
+
const handleTouchMove = react.useCallback((e) => {
|
|
174
|
+
const start = touchRef.current;
|
|
175
|
+
if (!start || e.touches.length !== 1) return;
|
|
176
|
+
const t = e.touches[0];
|
|
177
|
+
const dx = t.clientX - start.x;
|
|
178
|
+
const dy = t.clientY - start.y;
|
|
179
|
+
if (!start.axis && Math.abs(dx) + Math.abs(dy) > 10) {
|
|
180
|
+
start.axis = Math.abs(dx) > Math.abs(dy) ? "x" : "y";
|
|
181
|
+
}
|
|
182
|
+
if (start.axis === "x") {
|
|
183
|
+
setDrag({ x: dx, y: 0 });
|
|
184
|
+
} else if (start.axis === "y" && dy > 0) {
|
|
185
|
+
setDrag({ x: 0, y: dy });
|
|
186
|
+
}
|
|
187
|
+
}, []);
|
|
188
|
+
const handleTouchEnd = react.useCallback(() => {
|
|
189
|
+
const start = touchRef.current;
|
|
190
|
+
const offset = drag;
|
|
191
|
+
touchRef.current = null;
|
|
192
|
+
setDrag(null);
|
|
193
|
+
if (!start || !offset) return;
|
|
194
|
+
if (start.axis === "x" && Math.abs(offset.x) > SWIPE_THRESHOLD && count > 1) {
|
|
195
|
+
if (offset.x < 0) handleNext();
|
|
196
|
+
else handlePrev();
|
|
197
|
+
} else if (start.axis === "y" && offset.y > CLOSE_THRESHOLD) {
|
|
198
|
+
onClose();
|
|
199
|
+
}
|
|
200
|
+
}, [drag, count, handleNext, handlePrev, onClose]);
|
|
162
201
|
react.useEffect(() => {
|
|
163
202
|
const handleKeyDown = (e) => {
|
|
164
203
|
if (e.key === "ArrowLeft") handlePrev();
|
|
@@ -186,6 +225,11 @@ function MediaLightbox({
|
|
|
186
225
|
onClose();
|
|
187
226
|
}
|
|
188
227
|
};
|
|
228
|
+
const contentStyle = drag ? {
|
|
229
|
+
transform: `translate3d(${drag.x}px, ${drag.y}px, 0)`,
|
|
230
|
+
transition: "none"
|
|
231
|
+
} : {};
|
|
232
|
+
const overlayStyle = drag && drag.y > 0 ? { opacity: Math.max(0.3, 1 - drag.y / 400) } : {};
|
|
189
233
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
190
234
|
"div",
|
|
191
235
|
{
|
|
@@ -193,6 +237,7 @@ function MediaLightbox({
|
|
|
193
237
|
role: "dialog",
|
|
194
238
|
"aria-modal": "true",
|
|
195
239
|
onClick: handleOverlayClick,
|
|
240
|
+
style: overlayStyle,
|
|
196
241
|
children: [
|
|
197
242
|
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "rml-closeBtn", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) }),
|
|
198
243
|
count > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -204,15 +249,27 @@ function MediaLightbox({
|
|
|
204
249
|
children: /* @__PURE__ */ jsxRuntime.jsx(PrevIcon, {})
|
|
205
250
|
}
|
|
206
251
|
),
|
|
207
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
-
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
253
|
+
"div",
|
|
209
254
|
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
255
|
+
className: "rml-content",
|
|
256
|
+
onClick: handleOverlayClick,
|
|
257
|
+
onTouchStart: handleTouchStart,
|
|
258
|
+
onTouchMove: handleTouchMove,
|
|
259
|
+
onTouchEnd: handleTouchEnd,
|
|
260
|
+
onTouchCancel: handleTouchEnd,
|
|
261
|
+
style: contentStyle,
|
|
262
|
+
children: current.type === "video" ? /* @__PURE__ */ jsxRuntime.jsx(VideoSlide, { item: current }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
263
|
+
ImageSlide,
|
|
264
|
+
{
|
|
265
|
+
src: current.src,
|
|
266
|
+
alt: current.alt || current.title || `Item ${safeIndex + 1}`,
|
|
267
|
+
fallbackSrc
|
|
268
|
+
},
|
|
269
|
+
safeIndex
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
),
|
|
216
273
|
count > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
217
274
|
"button",
|
|
218
275
|
{
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/video.ts","../src/MediaLightbox.tsx"],"names":["jsx","useState","jsxs","useCallback","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { VideoProvider } from \"./types\";\n\n/** Detect a video provider from its URL. */\nexport function detectProvider(url: string): VideoProvider {\n if (!url) return \"file\";\n if (/(?:youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/i.test(url)) {\n return \"youtube\";\n }\n if (/vimeo\\.com/i.test(url)) {\n return \"vimeo\";\n }\n return \"file\";\n}\n\n/** Extract the YouTube video id from any common YouTube URL shape. */\nexport function getYouTubeId(url: string): string | null {\n if (!url) return null;\n try {\n if (url.includes(\"youtube.com/embed/\")) {\n return url.split(\"youtube.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube-nocookie.com/embed/\")) {\n return url.split(\"youtube-nocookie.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/v/\")) {\n return url.split(\"youtube.com/v/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtu.be/\")) {\n return url.split(\"youtu.be/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/watch\")) {\n const params = new URLSearchParams(new URL(url).search);\n return params.get(\"v\");\n }\n } catch {\n return null;\n }\n return null;\n}\n\n/** Extract the numeric Vimeo id from any common Vimeo URL shape. */\nexport function getVimeoId(url: string): string | null {\n if (!url) return null;\n if (url.includes(\"player.vimeo.com/video/\")) {\n return url.split(\"player.vimeo.com/video/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n const match = url.match(/vimeo\\.com\\/(?:.*\\/)?(\\d+)/i);\n return match ? match[1] : null;\n}\n\n/**\n * Resolve a video URL to an embeddable `<iframe>` src.\n * Returns `null` if the URL is not an iframe-embeddable provider (e.g. a file).\n */\nexport function getEmbedURL(\n url: string,\n provider: VideoProvider,\n autoplay: boolean\n): string | null {\n if (provider === \"youtube\") {\n const id = getYouTubeId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://www.youtube-nocookie.com/embed/${id}${params}`;\n }\n return url.replace(\"youtube.com\", \"youtube-nocookie.com\");\n }\n if (provider === \"vimeo\") {\n const id = getVimeoId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://player.vimeo.com/video/${id}${params}`;\n }\n return url;\n }\n return null;\n}\n","\"use client\";\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport type { MediaItem, MediaLightboxProps, VideoItem } from \"./types\";\nimport { detectProvider, getEmbedURL } from \"./video\";\n\nconst CloseIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\n </svg>\n);\n\nconst PrevIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n);\n\nconst NextIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n);\n\nfunction ImageSlide({\n src,\n alt,\n fallbackSrc,\n}: {\n src: string;\n alt: string;\n fallbackSrc?: string;\n}) {\n const [isLoaded, setIsLoaded] = useState(false);\n\n // Reset the loaded flag when the source changes (React-recommended derived\n // state via render-time comparison instead of an effect).\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (renderedSrc !== src) {\n setRenderedSrc(src);\n setIsLoaded(false);\n }\n\n return (\n <div className=\"rml-imageContainer\">\n {!isLoaded && <div className=\"rml-spinner\" />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className={`rml-image${isLoaded ? \" rml-imageLoaded\" : \"\"}`}\n src={src}\n alt={alt}\n draggable={false}\n onLoad={() => setIsLoaded(true)}\n onError={(e) => {\n setIsLoaded(true);\n if (fallbackSrc && e.currentTarget.src !== fallbackSrc) {\n e.currentTarget.src = fallbackSrc;\n }\n }}\n />\n </div>\n );\n}\n\nfunction VideoSlide({ item }: { item: VideoItem }) {\n const autoplay = item.autoplay ?? true;\n const provider =\n !item.provider || item.provider === \"auto\"\n ? detectProvider(item.src)\n : item.provider;\n\n if (provider === \"file\") {\n return (\n <div className=\"rml-playerContainer rml-fileContainer\">\n <video\n className=\"rml-videoPlayer\"\n src={item.src}\n poster={item.poster}\n controls\n autoPlay={autoplay}\n playsInline\n />\n </div>\n );\n }\n\n const embedUrl = getEmbedURL(item.src, provider, autoplay) ?? item.src;\n\n return (\n <div className=\"rml-playerContainer\">\n <iframe\n className=\"rml-iframePlayer\"\n src={embedUrl}\n title={item.title || \"Video player\"}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n );\n}\n\nexport function MediaLightbox({\n items,\n initialIndex = 0,\n onClose,\n fallbackSrc,\n showCounter = true,\n loop = true,\n closeOnOverlayClick = true,\n lockBodyScroll = true,\n className,\n onIndexChange,\n}: MediaLightboxProps) {\n const count = items?.length ?? 0;\n const [currentIndex, setCurrentIndex] = useState(initialIndex);\n\n const handlePrev = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === 0 ? (loop ? count - 1 : 0) : prev - 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n const handleNext = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === count - 1 ? (loop ? 0 : count - 1) : prev + 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") handlePrev();\n else if (e.key === \"ArrowRight\") handleNext();\n else if (e.key === \"Escape\") onClose();\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n let previousOverflow = \"\";\n if (lockBodyScroll) {\n previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n if (lockBodyScroll) {\n document.body.style.overflow = previousOverflow;\n }\n };\n }, [handlePrev, handleNext, onClose, lockBodyScroll]);\n\n if (!items || count === 0) return null;\n\n const safeIndex = Math.min(Math.max(currentIndex, 0), count - 1);\n const current: MediaItem = items[safeIndex];\n\n const handleOverlayClick = (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n };\n\n return (\n <div\n className={`rml-overlay${className ? ` ${className}` : \"\"}`}\n role=\"dialog\"\n aria-modal=\"true\"\n onClick={handleOverlayClick}\n >\n <button className=\"rml-closeBtn\" onClick={onClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-prevBtn\"\n onClick={handlePrev}\n aria-label=\"Previous\"\n >\n <PrevIcon />\n </button>\n )}\n\n <div className=\"rml-content\" onClick={handleOverlayClick}>\n {current.type === \"video\" ? (\n <VideoSlide item={current} />\n ) : (\n <ImageSlide\n key={safeIndex}\n src={current.src}\n alt={current.alt || current.title || `Item ${safeIndex + 1}`}\n fallbackSrc={fallbackSrc}\n />\n )}\n </div>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-nextBtn\"\n onClick={handleNext}\n aria-label=\"Next\"\n >\n <NextIcon />\n </button>\n )}\n\n <div className=\"rml-footer\">\n {showCounter && (\n <span className=\"rml-counter\">{`${safeIndex + 1} / ${count}`}</span>\n )}\n {current.title && <h4 className=\"rml-title\">{current.title}</h4>}\n </div>\n </div>\n );\n}\n\nexport default MediaLightbox;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/video.ts","../src/MediaLightbox.tsx"],"names":["jsx","useState","jsxs","useRef","useCallback","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { VideoProvider } from \"./types\";\n\n/** Detect a video provider from its URL. */\nexport function detectProvider(url: string): VideoProvider {\n if (!url) return \"file\";\n if (/(?:youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/i.test(url)) {\n return \"youtube\";\n }\n if (/vimeo\\.com/i.test(url)) {\n return \"vimeo\";\n }\n return \"file\";\n}\n\n/** Extract the YouTube video id from any common YouTube URL shape. */\nexport function getYouTubeId(url: string): string | null {\n if (!url) return null;\n try {\n if (url.includes(\"youtube.com/embed/\")) {\n return url.split(\"youtube.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube-nocookie.com/embed/\")) {\n return url.split(\"youtube-nocookie.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/v/\")) {\n return url.split(\"youtube.com/v/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtu.be/\")) {\n return url.split(\"youtu.be/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/watch\")) {\n const params = new URLSearchParams(new URL(url).search);\n return params.get(\"v\");\n }\n } catch {\n return null;\n }\n return null;\n}\n\n/** Extract the numeric Vimeo id from any common Vimeo URL shape. */\nexport function getVimeoId(url: string): string | null {\n if (!url) return null;\n if (url.includes(\"player.vimeo.com/video/\")) {\n return url.split(\"player.vimeo.com/video/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n const match = url.match(/vimeo\\.com\\/(?:.*\\/)?(\\d+)/i);\n return match ? match[1] : null;\n}\n\n/**\n * Resolve a video URL to an embeddable `<iframe>` src.\n * Returns `null` if the URL is not an iframe-embeddable provider (e.g. a file).\n */\nexport function getEmbedURL(\n url: string,\n provider: VideoProvider,\n autoplay: boolean\n): string | null {\n if (provider === \"youtube\") {\n const id = getYouTubeId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://www.youtube-nocookie.com/embed/${id}${params}`;\n }\n return url.replace(\"youtube.com\", \"youtube-nocookie.com\");\n }\n if (provider === \"vimeo\") {\n const id = getVimeoId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://player.vimeo.com/video/${id}${params}`;\n }\n return url;\n }\n return null;\n}\n","\"use client\";\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { MediaItem, MediaLightboxProps, VideoItem } from \"./types\";\nimport { detectProvider, getEmbedURL } from \"./video\";\n\n/** Minimum px travelled before a gesture is treated as a swipe. */\nconst SWIPE_THRESHOLD = 50;\n/** Vertical px travelled before a downward swipe closes the lightbox. */\nconst CLOSE_THRESHOLD = 110;\n\nconst CloseIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\n </svg>\n);\n\nconst PrevIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n);\n\nconst NextIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n);\n\nfunction ImageSlide({\n src,\n alt,\n fallbackSrc,\n}: {\n src: string;\n alt: string;\n fallbackSrc?: string;\n}) {\n const [isLoaded, setIsLoaded] = useState(false);\n\n // Reset the loaded flag when the source changes (React-recommended derived\n // state via render-time comparison instead of an effect).\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (renderedSrc !== src) {\n setRenderedSrc(src);\n setIsLoaded(false);\n }\n\n return (\n <div className=\"rml-imageContainer\">\n {!isLoaded && <div className=\"rml-spinner\" />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className={`rml-image${isLoaded ? \" rml-imageLoaded\" : \"\"}`}\n src={src}\n alt={alt}\n draggable={false}\n onLoad={() => setIsLoaded(true)}\n onError={(e) => {\n setIsLoaded(true);\n if (fallbackSrc && e.currentTarget.src !== fallbackSrc) {\n e.currentTarget.src = fallbackSrc;\n }\n }}\n />\n </div>\n );\n}\n\nfunction VideoSlide({ item }: { item: VideoItem }) {\n const autoplay = item.autoplay ?? true;\n const provider =\n !item.provider || item.provider === \"auto\"\n ? detectProvider(item.src)\n : item.provider;\n\n if (provider === \"file\") {\n return (\n <div className=\"rml-playerContainer rml-fileContainer\">\n <video\n className=\"rml-videoPlayer\"\n src={item.src}\n poster={item.poster}\n controls\n autoPlay={autoplay}\n playsInline\n />\n </div>\n );\n }\n\n const embedUrl = getEmbedURL(item.src, provider, autoplay) ?? item.src;\n\n return (\n <div className=\"rml-playerContainer\">\n <iframe\n className=\"rml-iframePlayer\"\n src={embedUrl}\n title={item.title || \"Video player\"}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n );\n}\n\nexport function MediaLightbox({\n items,\n initialIndex = 0,\n onClose,\n fallbackSrc,\n showCounter = true,\n loop = true,\n closeOnOverlayClick = true,\n lockBodyScroll = true,\n className,\n onIndexChange,\n}: MediaLightboxProps) {\n const count = items?.length ?? 0;\n const [currentIndex, setCurrentIndex] = useState(initialIndex);\n\n // Live drag offset (in px) used to follow the finger during a swipe.\n const [drag, setDrag] = useState<{ x: number; y: number } | null>(null);\n const touchRef = useRef<{ x: number; y: number; axis: \"x\" | \"y\" | null } | null>(\n null\n );\n\n const handlePrev = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === 0 ? (loop ? count - 1 : 0) : prev - 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n const handleNext = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === count - 1 ? (loop ? 0 : count - 1) : prev + 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n const handleTouchStart = useCallback((e: React.TouchEvent) => {\n if (e.touches.length !== 1) return;\n const t = e.touches[0];\n touchRef.current = { x: t.clientX, y: t.clientY, axis: null };\n }, []);\n\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\n const start = touchRef.current;\n if (!start || e.touches.length !== 1) return;\n const t = e.touches[0];\n const dx = t.clientX - start.x;\n const dy = t.clientY - start.y;\n\n // Lock the gesture to a single axis once movement is clearly intentional.\n if (!start.axis && Math.abs(dx) + Math.abs(dy) > 10) {\n start.axis = Math.abs(dx) > Math.abs(dy) ? \"x\" : \"y\";\n }\n\n if (start.axis === \"x\") {\n setDrag({ x: dx, y: 0 });\n } else if (start.axis === \"y\" && dy > 0) {\n // Only follow downward drags (swipe-to-close).\n setDrag({ x: 0, y: dy });\n }\n }, []);\n\n const handleTouchEnd = useCallback(() => {\n const start = touchRef.current;\n const offset = drag;\n touchRef.current = null;\n setDrag(null);\n if (!start || !offset) return;\n\n if (start.axis === \"x\" && Math.abs(offset.x) > SWIPE_THRESHOLD && count > 1) {\n if (offset.x < 0) handleNext();\n else handlePrev();\n } else if (start.axis === \"y\" && offset.y > CLOSE_THRESHOLD) {\n onClose();\n }\n }, [drag, count, handleNext, handlePrev, onClose]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") handlePrev();\n else if (e.key === \"ArrowRight\") handleNext();\n else if (e.key === \"Escape\") onClose();\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n let previousOverflow = \"\";\n if (lockBodyScroll) {\n previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n if (lockBodyScroll) {\n document.body.style.overflow = previousOverflow;\n }\n };\n }, [handlePrev, handleNext, onClose, lockBodyScroll]);\n\n if (!items || count === 0) return null;\n\n const safeIndex = Math.min(Math.max(currentIndex, 0), count - 1);\n const current: MediaItem = items[safeIndex];\n\n const handleOverlayClick = (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n };\n\n const contentStyle: React.CSSProperties = drag\n ? {\n transform: `translate3d(${drag.x}px, ${drag.y}px, 0)`,\n transition: \"none\",\n }\n : {};\n\n // Dim the backdrop as the user drags down to close.\n const overlayStyle: React.CSSProperties =\n drag && drag.y > 0\n ? { opacity: Math.max(0.3, 1 - drag.y / 400) }\n : {};\n\n return (\n <div\n className={`rml-overlay${className ? ` ${className}` : \"\"}`}\n role=\"dialog\"\n aria-modal=\"true\"\n onClick={handleOverlayClick}\n style={overlayStyle}\n >\n <button className=\"rml-closeBtn\" onClick={onClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-prevBtn\"\n onClick={handlePrev}\n aria-label=\"Previous\"\n >\n <PrevIcon />\n </button>\n )}\n\n <div\n className=\"rml-content\"\n onClick={handleOverlayClick}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleTouchEnd}\n onTouchCancel={handleTouchEnd}\n style={contentStyle}\n >\n {current.type === \"video\" ? (\n <VideoSlide item={current} />\n ) : (\n <ImageSlide\n key={safeIndex}\n src={current.src}\n alt={current.alt || current.title || `Item ${safeIndex + 1}`}\n fallbackSrc={fallbackSrc}\n />\n )}\n </div>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-nextBtn\"\n onClick={handleNext}\n aria-label=\"Next\"\n >\n <NextIcon />\n </button>\n )}\n\n <div className=\"rml-footer\">\n {showCounter && (\n <span className=\"rml-counter\">{`${safeIndex + 1} / ${count}`}</span>\n )}\n {current.title && <h4 className=\"rml-title\">{current.title}</h4>}\n </div>\n </div>\n );\n}\n\nexport default MediaLightbox;\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/MediaLightbox.tsx
|
|
@@ -65,6 +65,8 @@ function getEmbedURL(url, provider, autoplay) {
|
|
|
65
65
|
}
|
|
66
66
|
return null;
|
|
67
67
|
}
|
|
68
|
+
var SWIPE_THRESHOLD = 50;
|
|
69
|
+
var CLOSE_THRESHOLD = 110;
|
|
68
70
|
var CloseIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) });
|
|
69
71
|
var PrevIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) });
|
|
70
72
|
var NextIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }) });
|
|
@@ -141,6 +143,10 @@ function MediaLightbox({
|
|
|
141
143
|
}) {
|
|
142
144
|
const count = items?.length ?? 0;
|
|
143
145
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
146
|
+
const [drag, setDrag] = useState(null);
|
|
147
|
+
const touchRef = useRef(
|
|
148
|
+
null
|
|
149
|
+
);
|
|
144
150
|
const handlePrev = useCallback(() => {
|
|
145
151
|
setCurrentIndex((prev) => {
|
|
146
152
|
const next = prev === 0 ? loop ? count - 1 : 0 : prev - 1;
|
|
@@ -155,6 +161,39 @@ function MediaLightbox({
|
|
|
155
161
|
return next;
|
|
156
162
|
});
|
|
157
163
|
}, [count, loop, onIndexChange]);
|
|
164
|
+
const handleTouchStart = useCallback((e) => {
|
|
165
|
+
if (e.touches.length !== 1) return;
|
|
166
|
+
const t = e.touches[0];
|
|
167
|
+
touchRef.current = { x: t.clientX, y: t.clientY, axis: null };
|
|
168
|
+
}, []);
|
|
169
|
+
const handleTouchMove = useCallback((e) => {
|
|
170
|
+
const start = touchRef.current;
|
|
171
|
+
if (!start || e.touches.length !== 1) return;
|
|
172
|
+
const t = e.touches[0];
|
|
173
|
+
const dx = t.clientX - start.x;
|
|
174
|
+
const dy = t.clientY - start.y;
|
|
175
|
+
if (!start.axis && Math.abs(dx) + Math.abs(dy) > 10) {
|
|
176
|
+
start.axis = Math.abs(dx) > Math.abs(dy) ? "x" : "y";
|
|
177
|
+
}
|
|
178
|
+
if (start.axis === "x") {
|
|
179
|
+
setDrag({ x: dx, y: 0 });
|
|
180
|
+
} else if (start.axis === "y" && dy > 0) {
|
|
181
|
+
setDrag({ x: 0, y: dy });
|
|
182
|
+
}
|
|
183
|
+
}, []);
|
|
184
|
+
const handleTouchEnd = useCallback(() => {
|
|
185
|
+
const start = touchRef.current;
|
|
186
|
+
const offset = drag;
|
|
187
|
+
touchRef.current = null;
|
|
188
|
+
setDrag(null);
|
|
189
|
+
if (!start || !offset) return;
|
|
190
|
+
if (start.axis === "x" && Math.abs(offset.x) > SWIPE_THRESHOLD && count > 1) {
|
|
191
|
+
if (offset.x < 0) handleNext();
|
|
192
|
+
else handlePrev();
|
|
193
|
+
} else if (start.axis === "y" && offset.y > CLOSE_THRESHOLD) {
|
|
194
|
+
onClose();
|
|
195
|
+
}
|
|
196
|
+
}, [drag, count, handleNext, handlePrev, onClose]);
|
|
158
197
|
useEffect(() => {
|
|
159
198
|
const handleKeyDown = (e) => {
|
|
160
199
|
if (e.key === "ArrowLeft") handlePrev();
|
|
@@ -182,6 +221,11 @@ function MediaLightbox({
|
|
|
182
221
|
onClose();
|
|
183
222
|
}
|
|
184
223
|
};
|
|
224
|
+
const contentStyle = drag ? {
|
|
225
|
+
transform: `translate3d(${drag.x}px, ${drag.y}px, 0)`,
|
|
226
|
+
transition: "none"
|
|
227
|
+
} : {};
|
|
228
|
+
const overlayStyle = drag && drag.y > 0 ? { opacity: Math.max(0.3, 1 - drag.y / 400) } : {};
|
|
185
229
|
return /* @__PURE__ */ jsxs(
|
|
186
230
|
"div",
|
|
187
231
|
{
|
|
@@ -189,6 +233,7 @@ function MediaLightbox({
|
|
|
189
233
|
role: "dialog",
|
|
190
234
|
"aria-modal": "true",
|
|
191
235
|
onClick: handleOverlayClick,
|
|
236
|
+
style: overlayStyle,
|
|
192
237
|
children: [
|
|
193
238
|
/* @__PURE__ */ jsx("button", { className: "rml-closeBtn", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseIcon, {}) }),
|
|
194
239
|
count > 1 && /* @__PURE__ */ jsx(
|
|
@@ -200,15 +245,27 @@ function MediaLightbox({
|
|
|
200
245
|
children: /* @__PURE__ */ jsx(PrevIcon, {})
|
|
201
246
|
}
|
|
202
247
|
),
|
|
203
|
-
/* @__PURE__ */ jsx(
|
|
204
|
-
|
|
248
|
+
/* @__PURE__ */ jsx(
|
|
249
|
+
"div",
|
|
205
250
|
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
251
|
+
className: "rml-content",
|
|
252
|
+
onClick: handleOverlayClick,
|
|
253
|
+
onTouchStart: handleTouchStart,
|
|
254
|
+
onTouchMove: handleTouchMove,
|
|
255
|
+
onTouchEnd: handleTouchEnd,
|
|
256
|
+
onTouchCancel: handleTouchEnd,
|
|
257
|
+
style: contentStyle,
|
|
258
|
+
children: current.type === "video" ? /* @__PURE__ */ jsx(VideoSlide, { item: current }) : /* @__PURE__ */ jsx(
|
|
259
|
+
ImageSlide,
|
|
260
|
+
{
|
|
261
|
+
src: current.src,
|
|
262
|
+
alt: current.alt || current.title || `Item ${safeIndex + 1}`,
|
|
263
|
+
fallbackSrc
|
|
264
|
+
},
|
|
265
|
+
safeIndex
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
),
|
|
212
269
|
count > 1 && /* @__PURE__ */ jsx(
|
|
213
270
|
"button",
|
|
214
271
|
{
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/video.ts","../src/MediaLightbox.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { VideoProvider } from \"./types\";\n\n/** Detect a video provider from its URL. */\nexport function detectProvider(url: string): VideoProvider {\n if (!url) return \"file\";\n if (/(?:youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/i.test(url)) {\n return \"youtube\";\n }\n if (/vimeo\\.com/i.test(url)) {\n return \"vimeo\";\n }\n return \"file\";\n}\n\n/** Extract the YouTube video id from any common YouTube URL shape. */\nexport function getYouTubeId(url: string): string | null {\n if (!url) return null;\n try {\n if (url.includes(\"youtube.com/embed/\")) {\n return url.split(\"youtube.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube-nocookie.com/embed/\")) {\n return url.split(\"youtube-nocookie.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/v/\")) {\n return url.split(\"youtube.com/v/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtu.be/\")) {\n return url.split(\"youtu.be/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/watch\")) {\n const params = new URLSearchParams(new URL(url).search);\n return params.get(\"v\");\n }\n } catch {\n return null;\n }\n return null;\n}\n\n/** Extract the numeric Vimeo id from any common Vimeo URL shape. */\nexport function getVimeoId(url: string): string | null {\n if (!url) return null;\n if (url.includes(\"player.vimeo.com/video/\")) {\n return url.split(\"player.vimeo.com/video/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n const match = url.match(/vimeo\\.com\\/(?:.*\\/)?(\\d+)/i);\n return match ? match[1] : null;\n}\n\n/**\n * Resolve a video URL to an embeddable `<iframe>` src.\n * Returns `null` if the URL is not an iframe-embeddable provider (e.g. a file).\n */\nexport function getEmbedURL(\n url: string,\n provider: VideoProvider,\n autoplay: boolean\n): string | null {\n if (provider === \"youtube\") {\n const id = getYouTubeId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://www.youtube-nocookie.com/embed/${id}${params}`;\n }\n return url.replace(\"youtube.com\", \"youtube-nocookie.com\");\n }\n if (provider === \"vimeo\") {\n const id = getVimeoId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://player.vimeo.com/video/${id}${params}`;\n }\n return url;\n }\n return null;\n}\n","\"use client\";\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport type { MediaItem, MediaLightboxProps, VideoItem } from \"./types\";\nimport { detectProvider, getEmbedURL } from \"./video\";\n\nconst CloseIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\n </svg>\n);\n\nconst PrevIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n);\n\nconst NextIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n);\n\nfunction ImageSlide({\n src,\n alt,\n fallbackSrc,\n}: {\n src: string;\n alt: string;\n fallbackSrc?: string;\n}) {\n const [isLoaded, setIsLoaded] = useState(false);\n\n // Reset the loaded flag when the source changes (React-recommended derived\n // state via render-time comparison instead of an effect).\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (renderedSrc !== src) {\n setRenderedSrc(src);\n setIsLoaded(false);\n }\n\n return (\n <div className=\"rml-imageContainer\">\n {!isLoaded && <div className=\"rml-spinner\" />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className={`rml-image${isLoaded ? \" rml-imageLoaded\" : \"\"}`}\n src={src}\n alt={alt}\n draggable={false}\n onLoad={() => setIsLoaded(true)}\n onError={(e) => {\n setIsLoaded(true);\n if (fallbackSrc && e.currentTarget.src !== fallbackSrc) {\n e.currentTarget.src = fallbackSrc;\n }\n }}\n />\n </div>\n );\n}\n\nfunction VideoSlide({ item }: { item: VideoItem }) {\n const autoplay = item.autoplay ?? true;\n const provider =\n !item.provider || item.provider === \"auto\"\n ? detectProvider(item.src)\n : item.provider;\n\n if (provider === \"file\") {\n return (\n <div className=\"rml-playerContainer rml-fileContainer\">\n <video\n className=\"rml-videoPlayer\"\n src={item.src}\n poster={item.poster}\n controls\n autoPlay={autoplay}\n playsInline\n />\n </div>\n );\n }\n\n const embedUrl = getEmbedURL(item.src, provider, autoplay) ?? item.src;\n\n return (\n <div className=\"rml-playerContainer\">\n <iframe\n className=\"rml-iframePlayer\"\n src={embedUrl}\n title={item.title || \"Video player\"}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n );\n}\n\nexport function MediaLightbox({\n items,\n initialIndex = 0,\n onClose,\n fallbackSrc,\n showCounter = true,\n loop = true,\n closeOnOverlayClick = true,\n lockBodyScroll = true,\n className,\n onIndexChange,\n}: MediaLightboxProps) {\n const count = items?.length ?? 0;\n const [currentIndex, setCurrentIndex] = useState(initialIndex);\n\n const handlePrev = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === 0 ? (loop ? count - 1 : 0) : prev - 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n const handleNext = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === count - 1 ? (loop ? 0 : count - 1) : prev + 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") handlePrev();\n else if (e.key === \"ArrowRight\") handleNext();\n else if (e.key === \"Escape\") onClose();\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n let previousOverflow = \"\";\n if (lockBodyScroll) {\n previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n if (lockBodyScroll) {\n document.body.style.overflow = previousOverflow;\n }\n };\n }, [handlePrev, handleNext, onClose, lockBodyScroll]);\n\n if (!items || count === 0) return null;\n\n const safeIndex = Math.min(Math.max(currentIndex, 0), count - 1);\n const current: MediaItem = items[safeIndex];\n\n const handleOverlayClick = (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n };\n\n return (\n <div\n className={`rml-overlay${className ? ` ${className}` : \"\"}`}\n role=\"dialog\"\n aria-modal=\"true\"\n onClick={handleOverlayClick}\n >\n <button className=\"rml-closeBtn\" onClick={onClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-prevBtn\"\n onClick={handlePrev}\n aria-label=\"Previous\"\n >\n <PrevIcon />\n </button>\n )}\n\n <div className=\"rml-content\" onClick={handleOverlayClick}>\n {current.type === \"video\" ? (\n <VideoSlide item={current} />\n ) : (\n <ImageSlide\n key={safeIndex}\n src={current.src}\n alt={current.alt || current.title || `Item ${safeIndex + 1}`}\n fallbackSrc={fallbackSrc}\n />\n )}\n </div>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-nextBtn\"\n onClick={handleNext}\n aria-label=\"Next\"\n >\n <NextIcon />\n </button>\n )}\n\n <div className=\"rml-footer\">\n {showCounter && (\n <span className=\"rml-counter\">{`${safeIndex + 1} / ${count}`}</span>\n )}\n {current.title && <h4 className=\"rml-title\">{current.title}</h4>}\n </div>\n </div>\n );\n}\n\nexport default MediaLightbox;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/video.ts","../src/MediaLightbox.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { VideoProvider } from \"./types\";\n\n/** Detect a video provider from its URL. */\nexport function detectProvider(url: string): VideoProvider {\n if (!url) return \"file\";\n if (/(?:youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/i.test(url)) {\n return \"youtube\";\n }\n if (/vimeo\\.com/i.test(url)) {\n return \"vimeo\";\n }\n return \"file\";\n}\n\n/** Extract the YouTube video id from any common YouTube URL shape. */\nexport function getYouTubeId(url: string): string | null {\n if (!url) return null;\n try {\n if (url.includes(\"youtube.com/embed/\")) {\n return url.split(\"youtube.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube-nocookie.com/embed/\")) {\n return url.split(\"youtube-nocookie.com/embed/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/v/\")) {\n return url.split(\"youtube.com/v/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtu.be/\")) {\n return url.split(\"youtu.be/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n if (url.includes(\"youtube.com/watch\")) {\n const params = new URLSearchParams(new URL(url).search);\n return params.get(\"v\");\n }\n } catch {\n return null;\n }\n return null;\n}\n\n/** Extract the numeric Vimeo id from any common Vimeo URL shape. */\nexport function getVimeoId(url: string): string | null {\n if (!url) return null;\n if (url.includes(\"player.vimeo.com/video/\")) {\n return url.split(\"player.vimeo.com/video/\")[1]?.split(/[?&/]/)[0] ?? null;\n }\n const match = url.match(/vimeo\\.com\\/(?:.*\\/)?(\\d+)/i);\n return match ? match[1] : null;\n}\n\n/**\n * Resolve a video URL to an embeddable `<iframe>` src.\n * Returns `null` if the URL is not an iframe-embeddable provider (e.g. a file).\n */\nexport function getEmbedURL(\n url: string,\n provider: VideoProvider,\n autoplay: boolean\n): string | null {\n if (provider === \"youtube\") {\n const id = getYouTubeId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://www.youtube-nocookie.com/embed/${id}${params}`;\n }\n return url.replace(\"youtube.com\", \"youtube-nocookie.com\");\n }\n if (provider === \"vimeo\") {\n const id = getVimeoId(url);\n if (id) {\n const params = autoplay ? \"?autoplay=1\" : \"\";\n return `https://player.vimeo.com/video/${id}${params}`;\n }\n return url;\n }\n return null;\n}\n","\"use client\";\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { MediaItem, MediaLightboxProps, VideoItem } from \"./types\";\nimport { detectProvider, getEmbedURL } from \"./video\";\n\n/** Minimum px travelled before a gesture is treated as a swipe. */\nconst SWIPE_THRESHOLD = 50;\n/** Vertical px travelled before a downward swipe closes the lightbox. */\nconst CLOSE_THRESHOLD = 110;\n\nconst CloseIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\n </svg>\n);\n\nconst PrevIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n);\n\nconst NextIcon = () => (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n </svg>\n);\n\nfunction ImageSlide({\n src,\n alt,\n fallbackSrc,\n}: {\n src: string;\n alt: string;\n fallbackSrc?: string;\n}) {\n const [isLoaded, setIsLoaded] = useState(false);\n\n // Reset the loaded flag when the source changes (React-recommended derived\n // state via render-time comparison instead of an effect).\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (renderedSrc !== src) {\n setRenderedSrc(src);\n setIsLoaded(false);\n }\n\n return (\n <div className=\"rml-imageContainer\">\n {!isLoaded && <div className=\"rml-spinner\" />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className={`rml-image${isLoaded ? \" rml-imageLoaded\" : \"\"}`}\n src={src}\n alt={alt}\n draggable={false}\n onLoad={() => setIsLoaded(true)}\n onError={(e) => {\n setIsLoaded(true);\n if (fallbackSrc && e.currentTarget.src !== fallbackSrc) {\n e.currentTarget.src = fallbackSrc;\n }\n }}\n />\n </div>\n );\n}\n\nfunction VideoSlide({ item }: { item: VideoItem }) {\n const autoplay = item.autoplay ?? true;\n const provider =\n !item.provider || item.provider === \"auto\"\n ? detectProvider(item.src)\n : item.provider;\n\n if (provider === \"file\") {\n return (\n <div className=\"rml-playerContainer rml-fileContainer\">\n <video\n className=\"rml-videoPlayer\"\n src={item.src}\n poster={item.poster}\n controls\n autoPlay={autoplay}\n playsInline\n />\n </div>\n );\n }\n\n const embedUrl = getEmbedURL(item.src, provider, autoplay) ?? item.src;\n\n return (\n <div className=\"rml-playerContainer\">\n <iframe\n className=\"rml-iframePlayer\"\n src={embedUrl}\n title={item.title || \"Video player\"}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n );\n}\n\nexport function MediaLightbox({\n items,\n initialIndex = 0,\n onClose,\n fallbackSrc,\n showCounter = true,\n loop = true,\n closeOnOverlayClick = true,\n lockBodyScroll = true,\n className,\n onIndexChange,\n}: MediaLightboxProps) {\n const count = items?.length ?? 0;\n const [currentIndex, setCurrentIndex] = useState(initialIndex);\n\n // Live drag offset (in px) used to follow the finger during a swipe.\n const [drag, setDrag] = useState<{ x: number; y: number } | null>(null);\n const touchRef = useRef<{ x: number; y: number; axis: \"x\" | \"y\" | null } | null>(\n null\n );\n\n const handlePrev = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === 0 ? (loop ? count - 1 : 0) : prev - 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n const handleNext = useCallback(() => {\n setCurrentIndex((prev) => {\n const next = prev === count - 1 ? (loop ? 0 : count - 1) : prev + 1;\n onIndexChange?.(next);\n return next;\n });\n }, [count, loop, onIndexChange]);\n\n const handleTouchStart = useCallback((e: React.TouchEvent) => {\n if (e.touches.length !== 1) return;\n const t = e.touches[0];\n touchRef.current = { x: t.clientX, y: t.clientY, axis: null };\n }, []);\n\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\n const start = touchRef.current;\n if (!start || e.touches.length !== 1) return;\n const t = e.touches[0];\n const dx = t.clientX - start.x;\n const dy = t.clientY - start.y;\n\n // Lock the gesture to a single axis once movement is clearly intentional.\n if (!start.axis && Math.abs(dx) + Math.abs(dy) > 10) {\n start.axis = Math.abs(dx) > Math.abs(dy) ? \"x\" : \"y\";\n }\n\n if (start.axis === \"x\") {\n setDrag({ x: dx, y: 0 });\n } else if (start.axis === \"y\" && dy > 0) {\n // Only follow downward drags (swipe-to-close).\n setDrag({ x: 0, y: dy });\n }\n }, []);\n\n const handleTouchEnd = useCallback(() => {\n const start = touchRef.current;\n const offset = drag;\n touchRef.current = null;\n setDrag(null);\n if (!start || !offset) return;\n\n if (start.axis === \"x\" && Math.abs(offset.x) > SWIPE_THRESHOLD && count > 1) {\n if (offset.x < 0) handleNext();\n else handlePrev();\n } else if (start.axis === \"y\" && offset.y > CLOSE_THRESHOLD) {\n onClose();\n }\n }, [drag, count, handleNext, handlePrev, onClose]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") handlePrev();\n else if (e.key === \"ArrowRight\") handleNext();\n else if (e.key === \"Escape\") onClose();\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n let previousOverflow = \"\";\n if (lockBodyScroll) {\n previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n if (lockBodyScroll) {\n document.body.style.overflow = previousOverflow;\n }\n };\n }, [handlePrev, handleNext, onClose, lockBodyScroll]);\n\n if (!items || count === 0) return null;\n\n const safeIndex = Math.min(Math.max(currentIndex, 0), count - 1);\n const current: MediaItem = items[safeIndex];\n\n const handleOverlayClick = (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n };\n\n const contentStyle: React.CSSProperties = drag\n ? {\n transform: `translate3d(${drag.x}px, ${drag.y}px, 0)`,\n transition: \"none\",\n }\n : {};\n\n // Dim the backdrop as the user drags down to close.\n const overlayStyle: React.CSSProperties =\n drag && drag.y > 0\n ? { opacity: Math.max(0.3, 1 - drag.y / 400) }\n : {};\n\n return (\n <div\n className={`rml-overlay${className ? ` ${className}` : \"\"}`}\n role=\"dialog\"\n aria-modal=\"true\"\n onClick={handleOverlayClick}\n style={overlayStyle}\n >\n <button className=\"rml-closeBtn\" onClick={onClose} aria-label=\"Close\">\n <CloseIcon />\n </button>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-prevBtn\"\n onClick={handlePrev}\n aria-label=\"Previous\"\n >\n <PrevIcon />\n </button>\n )}\n\n <div\n className=\"rml-content\"\n onClick={handleOverlayClick}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleTouchEnd}\n onTouchCancel={handleTouchEnd}\n style={contentStyle}\n >\n {current.type === \"video\" ? (\n <VideoSlide item={current} />\n ) : (\n <ImageSlide\n key={safeIndex}\n src={current.src}\n alt={current.alt || current.title || `Item ${safeIndex + 1}`}\n fallbackSrc={fallbackSrc}\n />\n )}\n </div>\n\n {count > 1 && (\n <button\n className=\"rml-navBtn rml-nextBtn\"\n onClick={handleNext}\n aria-label=\"Next\"\n >\n <NextIcon />\n </button>\n )}\n\n <div className=\"rml-footer\">\n {showCounter && (\n <span className=\"rml-counter\">{`${safeIndex + 1} / ${count}`}</span>\n )}\n {current.title && <h4 className=\"rml-title\">{current.title}</h4>}\n </div>\n </div>\n );\n}\n\nexport default MediaLightbox;\n"]}
|
package/dist/styles.css
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
-webkit-backdrop-filter: blur(12px);
|
|
11
11
|
animation: rml-fadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
12
12
|
user-select: none;
|
|
13
|
+
-webkit-tap-highlight-color: transparent;
|
|
14
|
+
touch-action: none;
|
|
15
|
+
overscroll-behavior: contain;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
.rml-content {
|
|
@@ -20,6 +23,9 @@
|
|
|
20
23
|
align-items: center;
|
|
21
24
|
justify-content: center;
|
|
22
25
|
outline: none;
|
|
26
|
+
/* Smoothly snap back into place when a swipe is released. */
|
|
27
|
+
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
28
|
+
will-change: transform;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
/* ---------- Image slide ---------- */
|
|
@@ -30,6 +36,8 @@
|
|
|
30
36
|
display: flex;
|
|
31
37
|
align-items: center;
|
|
32
38
|
justify-content: center;
|
|
39
|
+
/* Let JS own horizontal/vertical swipe gestures over the image. */
|
|
40
|
+
touch-action: none;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
.rml-image {
|
|
@@ -77,8 +85,8 @@
|
|
|
77
85
|
/* ---------- Controls ---------- */
|
|
78
86
|
.rml-closeBtn {
|
|
79
87
|
position: absolute;
|
|
80
|
-
top: 30px;
|
|
81
|
-
right: 30px;
|
|
88
|
+
top: max(30px, env(safe-area-inset-top));
|
|
89
|
+
right: max(30px, env(safe-area-inset-right));
|
|
82
90
|
background: rgba(255, 255, 255, 0.08);
|
|
83
91
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
84
92
|
color: #fff;
|
|
@@ -94,10 +102,17 @@
|
|
|
94
102
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
@media (hover: hover) {
|
|
106
|
+
.rml-closeBtn:hover {
|
|
107
|
+
background: rgba(255, 255, 255, 0.2);
|
|
108
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
109
|
+
transform: scale(1.05);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.rml-closeBtn:active {
|
|
114
|
+
background: rgba(255, 255, 255, 0.28);
|
|
115
|
+
transform: scale(0.92);
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
.rml-closeBtn svg {
|
|
@@ -125,10 +140,17 @@
|
|
|
125
140
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
126
141
|
}
|
|
127
142
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
@media (hover: hover) {
|
|
144
|
+
.rml-navBtn:hover {
|
|
145
|
+
background: rgba(255, 255, 255, 0.18);
|
|
146
|
+
border-color: rgba(255, 255, 255, 0.25);
|
|
147
|
+
transform: translateY(-50%) scale(1.05);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.rml-navBtn:active {
|
|
152
|
+
background: rgba(255, 255, 255, 0.28);
|
|
153
|
+
transform: translateY(-50%) scale(0.92);
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
.rml-prevBtn {
|
|
@@ -148,7 +170,7 @@
|
|
|
148
170
|
/* ---------- Footer ---------- */
|
|
149
171
|
.rml-footer {
|
|
150
172
|
position: absolute;
|
|
151
|
-
bottom: 30px;
|
|
173
|
+
bottom: max(30px, env(safe-area-inset-bottom));
|
|
152
174
|
left: 0;
|
|
153
175
|
right: 0;
|
|
154
176
|
text-align: center;
|
|
@@ -212,41 +234,98 @@
|
|
|
212
234
|
|
|
213
235
|
/* ---------- Responsive ---------- */
|
|
214
236
|
@media (max-width: 768px) {
|
|
215
|
-
.rml-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
width: 40px;
|
|
219
|
-
height: 40px;
|
|
237
|
+
.rml-content {
|
|
238
|
+
width: 100vw;
|
|
239
|
+
height: 100dvh;
|
|
220
240
|
}
|
|
221
241
|
|
|
222
|
-
.rml-
|
|
242
|
+
.rml-closeBtn {
|
|
243
|
+
top: max(12px, env(safe-area-inset-top));
|
|
244
|
+
right: max(12px, env(safe-area-inset-right));
|
|
223
245
|
width: 44px;
|
|
224
246
|
height: 44px;
|
|
225
247
|
}
|
|
226
248
|
|
|
249
|
+
/* Bottom-center the nav buttons so they're reachable with the thumbs. */
|
|
250
|
+
.rml-navBtn {
|
|
251
|
+
top: auto;
|
|
252
|
+
bottom: max(74px, calc(env(safe-area-inset-bottom) + 64px));
|
|
253
|
+
transform: none;
|
|
254
|
+
width: 48px;
|
|
255
|
+
height: 48px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.rml-navBtn:active {
|
|
259
|
+
transform: scale(0.9);
|
|
260
|
+
}
|
|
261
|
+
|
|
227
262
|
.rml-prevBtn {
|
|
228
|
-
left:
|
|
263
|
+
left: 50%;
|
|
264
|
+
margin-left: -64px;
|
|
229
265
|
}
|
|
230
266
|
|
|
231
267
|
.rml-nextBtn {
|
|
232
|
-
right:
|
|
268
|
+
right: 50%;
|
|
269
|
+
margin-right: -64px;
|
|
233
270
|
}
|
|
234
271
|
|
|
235
272
|
.rml-navBtn svg {
|
|
236
|
-
width:
|
|
237
|
-
height:
|
|
273
|
+
width: 22px;
|
|
274
|
+
height: 22px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.rml-footer {
|
|
278
|
+
bottom: max(20px, calc(env(safe-area-inset-bottom) + 12px));
|
|
279
|
+
padding: 0 20px;
|
|
238
280
|
}
|
|
239
281
|
|
|
240
282
|
.rml-title {
|
|
241
283
|
font-size: 15px;
|
|
242
|
-
max-width:
|
|
284
|
+
max-width: 86%;
|
|
243
285
|
}
|
|
244
286
|
|
|
245
287
|
.rml-image {
|
|
246
|
-
max-
|
|
288
|
+
max-width: 96vw;
|
|
289
|
+
max-height: 82dvh;
|
|
290
|
+
border-radius: 4px;
|
|
247
291
|
}
|
|
248
292
|
|
|
249
293
|
.rml-playerContainer {
|
|
250
|
-
width:
|
|
294
|
+
width: 96vw;
|
|
295
|
+
border-radius: 8px;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Compact landscape phones: keep nav buttons on the sides instead. */
|
|
300
|
+
@media (max-width: 900px) and (orientation: landscape) {
|
|
301
|
+
.rml-navBtn {
|
|
302
|
+
top: 50%;
|
|
303
|
+
bottom: auto;
|
|
304
|
+
transform: translateY(-50%);
|
|
305
|
+
width: 44px;
|
|
306
|
+
height: 44px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.rml-prevBtn {
|
|
310
|
+
left: max(10px, env(safe-area-inset-left));
|
|
311
|
+
margin-left: 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.rml-nextBtn {
|
|
315
|
+
right: max(10px, env(safe-area-inset-right));
|
|
316
|
+
margin-right: 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.rml-image {
|
|
320
|
+
max-height: 88dvh;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@media (prefers-reduced-motion: reduce) {
|
|
325
|
+
.rml-overlay,
|
|
326
|
+
.rml-content,
|
|
327
|
+
.rml-image {
|
|
328
|
+
animation: none;
|
|
329
|
+
transition: none;
|
|
251
330
|
}
|
|
252
331
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-media-lightbox",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A lightweight, dependency-free React lightbox for images and video (YouTube, Vimeo, and self-hosted files).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
],
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"author": "Sreekanth Ramachandran <tech.sreekanth@gmail.com>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/techsreekanth/react-media-lightbox.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/techsreekanth/react-media-lightbox/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/techsreekanth/react-media-lightbox#readme",
|
|
18
26
|
"type": "module",
|
|
19
27
|
"sideEffects": [
|
|
20
28
|
"**/*.css"
|