yet-another-react-lightbox 1.7.0 → 1.8.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/README.md +1 -0
- package/dist/Lightbox.js +13 -8
- package/dist/core/components/ImageSlide.d.ts +4 -3
- package/dist/core/components/ImageSlide.js +3 -6
- package/dist/core/config.js +16 -0
- package/dist/core/hooks/index.d.ts +2 -0
- package/dist/core/hooks/index.js +2 -0
- package/dist/core/hooks/useMotionPreference.d.ts +1 -0
- package/dist/core/hooks/useMotionPreference.js +12 -0
- package/dist/core/hooks/useRTL.d.ts +1 -0
- package/dist/core/hooks/useRTL.js +9 -0
- package/dist/core/modules/Carousel.js +4 -3
- package/dist/core/modules/Controller.d.ts +0 -2
- package/dist/core/modules/Controller.js +26 -25
- package/dist/core/modules/Navigation.js +5 -3
- package/dist/plugins/Fullscreen.d.ts +3 -2
- package/dist/plugins/Fullscreen.js +12 -3
- package/dist/plugins/Inline.js +2 -2
- package/dist/plugins/Thumbnails.d.ts +51 -0
- package/dist/plugins/Thumbnails.js +241 -0
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/thumbnails.css +151 -0
- package/dist/styles.css +6 -7
- package/dist/types.d.ts +13 -7
- package/dist/types.js +1 -0
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -132,6 +132,7 @@ The following plugins come bundled in the package:
|
|
|
132
132
|
- [Fullscreen](https://yet-another-react-lightbox.vercel.app/plugins/fullscreen) - adds support for fullscreen mode
|
|
133
133
|
- [Inline](https://yet-another-react-lightbox.vercel.app/plugins/inline) - adds support for inline rendering mode
|
|
134
134
|
- [Slideshow](https://yet-another-react-lightbox.vercel.app/plugins/slideshow) - adds slideshow autoplay feature
|
|
135
|
+
- [Thumbnails](https://yet-another-react-lightbox.vercel.app/plugins/thumbnails) - adds thumbnails track
|
|
135
136
|
- [Video](https://yet-another-react-lightbox.vercel.app/plugins/video) - adds support for video slides
|
|
136
137
|
|
|
137
138
|
## License
|
package/dist/Lightbox.js
CHANGED
|
@@ -5,8 +5,9 @@ const renderNode = (node, props) => {
|
|
|
5
5
|
var _a;
|
|
6
6
|
return React.createElement(node.module.component, { key: node.module.name, ...props }, (_a = node.children) === null || _a === void 0 ? void 0 : _a.map((child) => renderNode(child, props)));
|
|
7
7
|
};
|
|
8
|
-
const
|
|
9
|
-
const { plugins } = props;
|
|
8
|
+
export const Lightbox = (props) => {
|
|
9
|
+
const { carousel, animation, render, toolbar, controller, on, plugins, ...restProps } = props;
|
|
10
|
+
const { carousel: defaultCarousel, animation: defaultAnimation, render: defaultRender, toolbar: defaultToolbar, controller: defaultController, on: defaultOn, ...restDefaultProps } = LightboxDefaultProps;
|
|
10
11
|
const { config, augmentation } = withPlugins([
|
|
11
12
|
createNode(PortalModule, [
|
|
12
13
|
createNode(NoScrollModule, [
|
|
@@ -18,13 +19,17 @@ const LightboxComponent = (props) => {
|
|
|
18
19
|
]),
|
|
19
20
|
]),
|
|
20
21
|
], plugins);
|
|
21
|
-
const augmentedProps = augmentation(
|
|
22
|
+
const augmentedProps = augmentation({
|
|
23
|
+
carousel: { ...defaultCarousel, ...carousel },
|
|
24
|
+
animation: { ...defaultAnimation, ...animation },
|
|
25
|
+
render: { ...defaultRender, ...render },
|
|
26
|
+
toolbar: { ...defaultToolbar, ...toolbar },
|
|
27
|
+
controller: { ...defaultController, ...controller },
|
|
28
|
+
on: { ...defaultOn, ...on },
|
|
29
|
+
...restDefaultProps,
|
|
30
|
+
...restProps,
|
|
31
|
+
});
|
|
22
32
|
if (!augmentedProps.open)
|
|
23
33
|
return null;
|
|
24
34
|
return React.createElement(React.Fragment, null, renderNode(createNode(CoreModule, config), augmentedProps));
|
|
25
35
|
};
|
|
26
|
-
export const Lightbox = (props) => {
|
|
27
|
-
const { carousel, animation, render, toolbar, controller, on, ...restProps } = props;
|
|
28
|
-
const { carousel: defaultCarousel, animation: defaultAnimation, render: defaultRender, toolbar: defaultToolbar, controller: defaultController, on: defaultOn, ...restDefaultProps } = LightboxDefaultProps;
|
|
29
|
-
return (React.createElement(LightboxComponent, { carousel: { ...defaultCarousel, ...carousel }, animation: { ...defaultAnimation, ...animation }, render: { ...defaultRender, ...render }, toolbar: { ...defaultToolbar, ...toolbar }, controller: { ...defaultController, ...controller }, on: { ...defaultOn, ...on }, ...restDefaultProps, ...restProps }));
|
|
30
|
-
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { Render, SlideImage } from "../../types.js";
|
|
2
|
+
import { ImageFit, Render, SlideImage } from "../../types.js";
|
|
3
3
|
import { ContainerRect } from "../hooks/index.js";
|
|
4
4
|
export declare type ImageSlideProps = {
|
|
5
5
|
slide: SlideImage;
|
|
6
|
-
offset
|
|
6
|
+
offset?: number;
|
|
7
7
|
render?: Render;
|
|
8
8
|
rect?: ContainerRect;
|
|
9
|
+
imageFit?: ImageFit;
|
|
9
10
|
};
|
|
10
|
-
export declare const ImageSlide: ({ slide: image, offset, render, rect }: ImageSlideProps) => JSX.Element;
|
|
11
|
+
export declare const ImageSlide: ({ slide: image, offset, render, rect, imageFit }: ImageSlideProps) => JSX.Element;
|
|
@@ -3,13 +3,11 @@ import { adjustDevicePixelRatio, clsx, cssClass, hasWindow } from "../utils.js";
|
|
|
3
3
|
import { useLatest } from "../hooks/index.js";
|
|
4
4
|
import { useEvents } from "../contexts/index.js";
|
|
5
5
|
import { ErrorIcon, LoadingIcon } from "./Icons.js";
|
|
6
|
-
import { activeSlideStatus, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_LOADING } from "../consts.js";
|
|
7
|
-
|
|
8
|
-
export const ImageSlide = ({ slide: image, offset, render, rect }) => {
|
|
6
|
+
import { activeSlideStatus, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_LOADING, } from "../consts.js";
|
|
7
|
+
export const ImageSlide = ({ slide: image, offset, render, rect, imageFit }) => {
|
|
9
8
|
var _a;
|
|
10
9
|
const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
|
|
11
10
|
const latestStatus = useLatest(status);
|
|
12
|
-
const { latestProps } = useController();
|
|
13
11
|
const { publish } = useEvents();
|
|
14
12
|
const imageRef = React.useRef(null);
|
|
15
13
|
React.useEffect(() => {
|
|
@@ -43,8 +41,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect }) => {
|
|
|
43
41
|
setStatus(SLIDE_STATUS_ERROR);
|
|
44
42
|
}, []);
|
|
45
43
|
return (React.createElement(React.Fragment, null,
|
|
46
|
-
React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, className: clsx(cssClass("slide_image"), (image.imageFit === "cover" ||
|
|
47
|
-
(image.imageFit !== "contain" && latestProps.current.carousel.imageFit === "cover")) &&
|
|
44
|
+
React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, className: clsx(cssClass("slide_image"), cssClass("fullsize"), (image.imageFit === "cover" || (image.imageFit !== "contain" && imageFit === "cover")) &&
|
|
48
45
|
cssClass("slide_image_cover"), status !== SLIDE_STATUS_COMPLETE && cssClass("slide_image_loading")), draggable: false, alt: image.alt, ...(image.srcSet
|
|
49
46
|
? {
|
|
50
47
|
...(rect && hasWindow()
|
package/dist/core/config.js
CHANGED
|
@@ -21,6 +21,17 @@ const traverse = (nodes, target, apply) => nodes.flatMap((node) => { var _a; ret
|
|
|
21
21
|
export const withPlugins = (root, plugins) => {
|
|
22
22
|
let config = root;
|
|
23
23
|
const augmentations = [];
|
|
24
|
+
const contains = (target) => {
|
|
25
|
+
const nodes = [...config];
|
|
26
|
+
while (nodes.length > 0) {
|
|
27
|
+
const node = nodes.pop();
|
|
28
|
+
if ((node === null || node === void 0 ? void 0 : node.module.name) === target)
|
|
29
|
+
return true;
|
|
30
|
+
if (node === null || node === void 0 ? void 0 : node.children)
|
|
31
|
+
nodes.push(...node.children);
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
24
35
|
const addParent = (target, module) => {
|
|
25
36
|
if (target === "") {
|
|
26
37
|
config = [createNode(module, config)];
|
|
@@ -28,6 +39,9 @@ export const withPlugins = (root, plugins) => {
|
|
|
28
39
|
}
|
|
29
40
|
config = traverse(config, target, (node) => [createNode(module, [node])]);
|
|
30
41
|
};
|
|
42
|
+
const append = (target, module) => {
|
|
43
|
+
config = traverse(config, target, (node) => [createNode(node.module, [createNode(module, node.children)])]);
|
|
44
|
+
};
|
|
31
45
|
const addChild = (target, module, precede) => {
|
|
32
46
|
config = traverse(config, target, (node) => {
|
|
33
47
|
var _a;
|
|
@@ -58,7 +72,9 @@ export const withPlugins = (root, plugins) => {
|
|
|
58
72
|
};
|
|
59
73
|
plugins === null || plugins === void 0 ? void 0 : plugins.forEach((plugin) => {
|
|
60
74
|
plugin({
|
|
75
|
+
contains,
|
|
61
76
|
addParent,
|
|
77
|
+
append,
|
|
62
78
|
addChild,
|
|
63
79
|
addSibling,
|
|
64
80
|
replace,
|
package/dist/core/hooks/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useMotionPreference: () => boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useEnhancedEffect } from "./useEnhancedEffect.js";
|
|
3
|
+
export const useMotionPreference = () => {
|
|
4
|
+
const [reduceMotion, setReduceMotion] = React.useState(false);
|
|
5
|
+
useEnhancedEffect(() => {
|
|
6
|
+
var _a;
|
|
7
|
+
const mediaQuery = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)");
|
|
8
|
+
mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.addEventListener("change", () => setReduceMotion(mediaQuery.matches));
|
|
9
|
+
setReduceMotion(mediaQuery === null || mediaQuery === void 0 ? void 0 : mediaQuery.matches);
|
|
10
|
+
}, []);
|
|
11
|
+
return reduceMotion;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useRTL: () => boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useEnhancedEffect } from "./useEnhancedEffect.js";
|
|
3
|
+
export const useRTL = () => {
|
|
4
|
+
const [isRTL, setIsRTL] = React.useState(false);
|
|
5
|
+
useEnhancedEffect(() => {
|
|
6
|
+
setIsRTL(window.getComputedStyle(window.document.documentElement).direction === "rtl");
|
|
7
|
+
}, []);
|
|
8
|
+
return isRTL;
|
|
9
|
+
};
|
|
@@ -13,7 +13,7 @@ const CarouselSlide = ({ slide, offset }) => {
|
|
|
13
13
|
var _a, _b, _c, _d;
|
|
14
14
|
let rendered = (_a = render.slide) === null || _a === void 0 ? void 0 : _a.call(render, slide, offset, rect);
|
|
15
15
|
if (!rendered && "src" in slide) {
|
|
16
|
-
rendered = React.createElement(ImageSlide, { slide: slide, offset: offset, render: render, rect: rect });
|
|
16
|
+
rendered = (React.createElement(ImageSlide, { slide: slide, offset: offset, render: render, rect: rect, imageFit: latestProps.current.carousel.imageFit }));
|
|
17
17
|
}
|
|
18
18
|
return rendered ? (React.createElement(React.Fragment, null, (_b = render.slideHeader) === null || _b === void 0 ? void 0 :
|
|
19
19
|
_b.call(render, slide),
|
|
@@ -38,12 +38,13 @@ export const Carousel = ({ slides, carousel: { finite, preload, padding, spacing
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
const sanitize = (value) => value === 0 || value.trim() === "" || value.trim() === "0" ? "0px" : value;
|
|
41
42
|
return (React.createElement("div", { className: cssClass("carousel"), style: {
|
|
42
43
|
...(padding !== LightboxDefaultProps.carousel.padding
|
|
43
|
-
? { [cssVar("carousel_padding")]: padding }
|
|
44
|
+
? { [cssVar("carousel_padding")]: sanitize(padding) }
|
|
44
45
|
: null),
|
|
45
46
|
...(spacing !== LightboxDefaultProps.carousel.spacing
|
|
46
|
-
? { [cssVar("carousel_spacing")]: spacing
|
|
47
|
+
? { [cssVar("carousel_spacing")]: sanitize(spacing) }
|
|
47
48
|
: null),
|
|
48
49
|
} }, items));
|
|
49
50
|
};
|
|
@@ -4,11 +4,9 @@ import { SubscribeSensors } from "../hooks/index.js";
|
|
|
4
4
|
declare type ControllerState = {
|
|
5
5
|
currentIndex: number;
|
|
6
6
|
globalIndex: number;
|
|
7
|
-
isRTL: boolean;
|
|
8
7
|
};
|
|
9
8
|
export declare type ControllerContextType = ControllerState & {
|
|
10
9
|
latestProps: React.MutableRefObject<ComponentProps>;
|
|
11
|
-
containerRef: React.RefObject<HTMLDivElement>;
|
|
12
10
|
subscribeSensors: SubscribeSensors<HTMLDivElement>;
|
|
13
11
|
};
|
|
14
12
|
export declare const useController: () => ControllerContextType;
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { LightboxDefaultProps } from "../../types.js";
|
|
3
3
|
import { cleanup, clsx, cssClass, cssVar, makeUseContext } from "../utils.js";
|
|
4
4
|
import { createModule } from "../config.js";
|
|
5
|
-
import { useContainerRect, useEnhancedEffect, useLatest, useSensors } from "../hooks/index.js";
|
|
5
|
+
import { useContainerRect, useEnhancedEffect, useLatest, useRTL, useSensors, } from "../hooks/index.js";
|
|
6
6
|
import { useEvents, useTimeouts } from "../contexts/index.js";
|
|
7
7
|
const SWIPE_OFFSET_THRESHOLD = 30;
|
|
8
8
|
const ControllerContext = React.createContext(null);
|
|
@@ -12,10 +12,10 @@ export const Controller = ({ children, ...props }) => {
|
|
|
12
12
|
const { registerSensors, subscribeSensors } = useSensors();
|
|
13
13
|
const { subscribe, publish } = useEvents();
|
|
14
14
|
const { setTimeout, clearTimeout } = useTimeouts();
|
|
15
|
+
const isRTL = useLatest(useRTL());
|
|
15
16
|
const [state, setState] = React.useState({
|
|
16
17
|
currentIndex: props.index,
|
|
17
18
|
globalIndex: props.index,
|
|
18
|
-
isRTL: false,
|
|
19
19
|
});
|
|
20
20
|
const latestProps = useLatest(props);
|
|
21
21
|
const refs = React.useRef({
|
|
@@ -45,12 +45,6 @@ export const Controller = ({ children, ...props }) => {
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
}, [containerRef]);
|
|
48
|
-
useEnhancedEffect(() => {
|
|
49
|
-
const node = containerRef.current;
|
|
50
|
-
if (node) {
|
|
51
|
-
setState((prev) => ({ ...prev, isRTL: window.getComputedStyle(node).direction === "rtl" }));
|
|
52
|
-
}
|
|
53
|
-
}, [containerRef]);
|
|
54
48
|
React.useEffect(() => {
|
|
55
49
|
var _a;
|
|
56
50
|
if (refs.current.props.controller.focus) {
|
|
@@ -87,13 +81,13 @@ export const Controller = ({ children, ...props }) => {
|
|
|
87
81
|
clearTimeout(current.swipeIntentCleanup);
|
|
88
82
|
current.swipeIntentCleanup = undefined;
|
|
89
83
|
}, [clearTimeout]);
|
|
90
|
-
const rtl = React.useCallback((value) => (
|
|
84
|
+
const rtl = React.useCallback((value) => (isRTL.current ? -1 : 1) * (typeof value === "number" ? value : 1), [isRTL]);
|
|
91
85
|
const isSwipeValid = React.useCallback((offset) => {
|
|
92
86
|
const { state: { currentIndex }, props: { carousel, slides }, } = refs.current;
|
|
93
87
|
return !(carousel.finite &&
|
|
94
88
|
((rtl(offset) > 0 && currentIndex === 0) || (rtl(offset) < 0 && currentIndex === slides.length - 1)));
|
|
95
89
|
}, [rtl]);
|
|
96
|
-
const swipe = React.useCallback((direction) => {
|
|
90
|
+
const swipe = React.useCallback((direction, count = 1) => {
|
|
97
91
|
var _a;
|
|
98
92
|
const { current } = refs;
|
|
99
93
|
const slidesCount = current.props.slides.length;
|
|
@@ -101,7 +95,7 @@ export const Controller = ({ children, ...props }) => {
|
|
|
101
95
|
const { currentIndex, globalIndex } = current.state;
|
|
102
96
|
const { swipeOffset } = current;
|
|
103
97
|
let newSwipeState = "swipe-animation";
|
|
104
|
-
let newSwipeAnimationDuration = swipeAnimationDuration;
|
|
98
|
+
let newSwipeAnimationDuration = swipeAnimationDuration * count;
|
|
105
99
|
if (!direction) {
|
|
106
100
|
const containerWidth = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth;
|
|
107
101
|
const elapsedTime = current.swipeStartTime ? Date.now() - current.swipeStartTime : 0;
|
|
@@ -126,8 +120,8 @@ export const Controller = ({ children, ...props }) => {
|
|
|
126
120
|
const newState = {};
|
|
127
121
|
if (direction === "prev") {
|
|
128
122
|
if (isSwipeValid(rtl(1))) {
|
|
129
|
-
newState.currentIndex = (currentIndex -
|
|
130
|
-
newState.globalIndex = globalIndex -
|
|
123
|
+
newState.currentIndex = (currentIndex - count + slidesCount) % slidesCount;
|
|
124
|
+
newState.globalIndex = globalIndex - count;
|
|
131
125
|
}
|
|
132
126
|
else {
|
|
133
127
|
newSwipeState = undefined;
|
|
@@ -136,14 +130,15 @@ export const Controller = ({ children, ...props }) => {
|
|
|
136
130
|
}
|
|
137
131
|
else if (direction === "next") {
|
|
138
132
|
if (isSwipeValid(rtl(-1))) {
|
|
139
|
-
newState.currentIndex = (currentIndex +
|
|
140
|
-
newState.globalIndex = globalIndex +
|
|
133
|
+
newState.currentIndex = (currentIndex + count) % slidesCount;
|
|
134
|
+
newState.globalIndex = globalIndex + count;
|
|
141
135
|
}
|
|
142
136
|
else {
|
|
143
137
|
newSwipeState = undefined;
|
|
144
138
|
newSwipeAnimationDuration = swipeAnimationDuration;
|
|
145
139
|
}
|
|
146
140
|
}
|
|
141
|
+
newSwipeAnimationDuration = Math.round(newSwipeAnimationDuration);
|
|
147
142
|
resetSwipe();
|
|
148
143
|
current.swipeState = newSwipeState;
|
|
149
144
|
current.swipeAnimationDuration = newSwipeAnimationDuration;
|
|
@@ -154,9 +149,10 @@ export const Controller = ({ children, ...props }) => {
|
|
|
154
149
|
rerender();
|
|
155
150
|
}, newSwipeAnimationDuration);
|
|
156
151
|
}
|
|
152
|
+
publish("controller-swipe", { ...newState, animationDuration: current.swipeAnimationDuration });
|
|
157
153
|
setState((prev) => ({ ...prev, ...newState }));
|
|
158
|
-
}, [setTimeout, resetSwipe, isSwipeValid, rerender, containerRef, rtl]);
|
|
159
|
-
React.useEffect(() => cleanup(subscribe("prev", () => swipe("prev")), subscribe("next", () => swipe("next"))), [subscribe, swipe]);
|
|
154
|
+
}, [setTimeout, resetSwipe, isSwipeValid, rerender, containerRef, rtl, publish]);
|
|
155
|
+
React.useEffect(() => cleanup(subscribe("prev", (_, count) => swipe("prev", typeof count === "number" ? count : undefined)), subscribe("next", (_, count) => swipe("next", typeof count === "number" ? count : undefined))), [subscribe, swipe]);
|
|
160
156
|
React.useEffect(() => subscribeSensors("onKeyUp", (event) => {
|
|
161
157
|
if (event.code === "Escape") {
|
|
162
158
|
publish("close");
|
|
@@ -275,17 +271,22 @@ export const Controller = ({ children, ...props }) => {
|
|
|
275
271
|
React.useEffect(() => subscribeSensors("onWheel", onWheel), [subscribeSensors, onWheel]);
|
|
276
272
|
const context = React.useMemo(() => ({
|
|
277
273
|
latestProps,
|
|
278
|
-
containerRef,
|
|
279
274
|
currentIndex: state.currentIndex,
|
|
280
275
|
globalIndex: state.globalIndex,
|
|
281
|
-
isRTL: state.isRTL,
|
|
282
276
|
subscribeSensors,
|
|
283
|
-
}), [latestProps,
|
|
284
|
-
return (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass("container"), refs.current.swipeState === "swipe" && cssClass("container_swipe")), style:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
277
|
+
}), [latestProps, state.currentIndex, state.globalIndex, subscribeSensors]);
|
|
278
|
+
return (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass("container"), cssClass("fullsize"), refs.current.swipeState === "swipe" && cssClass("container_swipe")), style: {
|
|
279
|
+
...(refs.current.swipeAnimationDuration !== LightboxDefaultProps.animation.swipe
|
|
280
|
+
? {
|
|
281
|
+
[cssVar("swipe_animation_duration")]: `${Math.round(refs.current.swipeAnimationDuration)}ms`,
|
|
282
|
+
}
|
|
283
|
+
: null),
|
|
284
|
+
...(props.controller.touchAction !== "none"
|
|
285
|
+
? {
|
|
286
|
+
[cssVar("controller_touch_action")]: props.controller.touchAction,
|
|
287
|
+
}
|
|
288
|
+
: null),
|
|
289
|
+
}, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors },
|
|
289
290
|
React.createElement(ControllerContext.Provider, { value: context }, children)));
|
|
290
291
|
};
|
|
291
292
|
export const ControllerModule = createModule("controller", Controller);
|
|
@@ -4,18 +4,20 @@ import { cssClass, label as translateLabel } from "../utils.js";
|
|
|
4
4
|
import { IconButton, NextIcon, PreviousIcon } from "../components/index.js";
|
|
5
5
|
import { useEvents } from "../contexts/index.js";
|
|
6
6
|
import { useController } from "./Controller.js";
|
|
7
|
+
import { useLatest, useRTL } from "../hooks/index.js";
|
|
7
8
|
export const NavigationButton = ({ publish, labels, label, icon, renderIcon, action, disabled, }) => (React.createElement(IconButton, { label: translateLabel(labels, label), icon: icon, renderIcon: renderIcon, className: cssClass(`navigation_${action}`), disabled: disabled, "aria-disabled": disabled, onClick: () => {
|
|
8
9
|
publish(action);
|
|
9
10
|
} }));
|
|
10
11
|
export const Navigation = ({ slides, carousel: { finite }, labels, render: { buttonPrev, buttonNext, iconPrev, iconNext }, }) => {
|
|
11
|
-
const { currentIndex, subscribeSensors
|
|
12
|
+
const { currentIndex, subscribeSensors } = useController();
|
|
12
13
|
const { publish } = useEvents();
|
|
14
|
+
const isRTL = useLatest(useRTL());
|
|
13
15
|
React.useEffect(() => subscribeSensors("onKeyUp", (event) => {
|
|
14
16
|
if (event.code === "ArrowLeft") {
|
|
15
|
-
publish(isRTL ? "next" : "prev");
|
|
17
|
+
publish(isRTL.current ? "next" : "prev");
|
|
16
18
|
}
|
|
17
19
|
else if (event.code === "ArrowRight") {
|
|
18
|
-
publish(isRTL ? "prev" : "next");
|
|
20
|
+
publish(isRTL.current ? "prev" : "next");
|
|
19
21
|
}
|
|
20
22
|
}), [subscribeSensors, publish, isRTL]);
|
|
21
23
|
return (React.createElement(React.Fragment, null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { LightboxProps, Plugin, Render } from "../types.js";
|
|
2
|
+
import { Component, LightboxProps, Plugin, Render } from "../types.js";
|
|
3
3
|
declare module "../types.js" {
|
|
4
4
|
interface LightboxProps {
|
|
5
5
|
/** if `true`, enter fullscreen mode automatically when the lightbox opens */
|
|
@@ -35,13 +35,14 @@ declare global {
|
|
|
35
35
|
msRequestFullscreen?: () => void;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
export declare const FullscreenContainer: Component;
|
|
38
39
|
/** Fullscreen button props */
|
|
39
40
|
export declare type FullscreenButtonProps = Pick<LightboxProps, "labels"> & {
|
|
40
41
|
auto: boolean;
|
|
41
42
|
render: Render;
|
|
42
43
|
};
|
|
43
44
|
/** Fullscreen button */
|
|
44
|
-
export declare const FullscreenButton:
|
|
45
|
+
export declare const FullscreenButton: React.FC<FullscreenButtonProps>;
|
|
45
46
|
/** Fullscreen plugin */
|
|
46
47
|
export declare const Fullscreen: Plugin;
|
|
47
48
|
export default Fullscreen;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { createIcon, IconButton, label,
|
|
2
|
+
import { clsx, createIcon, createModule, cssClass, IconButton, label, makeUseContext, useLatest, } from "../core/index.js";
|
|
3
|
+
const FullscreenContext = React.createContext(null);
|
|
4
|
+
const useFullscreen = makeUseContext("useFullscreen", "FullscreenContext", FullscreenContext);
|
|
5
|
+
export const FullscreenContainer = ({ children }) => {
|
|
6
|
+
const containerRef = React.useRef(null);
|
|
7
|
+
const context = React.useMemo(() => ({ containerRef }), []);
|
|
8
|
+
return (React.createElement("div", { ref: containerRef, className: clsx(cssClass("fullscreen"), cssClass("fullsize")) },
|
|
9
|
+
React.createElement(FullscreenContext.Provider, { value: context }, children)));
|
|
10
|
+
};
|
|
3
11
|
const EnterFullscreenIcon = createIcon("EnterFullscreen", React.createElement("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }));
|
|
4
12
|
const ExitFullscreenIcon = createIcon("ExitFullscreen", React.createElement("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }));
|
|
5
13
|
export const FullscreenButton = ({ auto, labels, render }) => {
|
|
6
14
|
const [mounted, setMounted] = React.useState(false);
|
|
7
15
|
const [fullscreen, setFullscreen] = React.useState(false);
|
|
8
16
|
const latestAuto = useLatest(auto);
|
|
9
|
-
const { containerRef } =
|
|
17
|
+
const { containerRef } = useFullscreen();
|
|
10
18
|
const isFullscreenEnabled = () => {
|
|
11
19
|
var _a, _b, _c, _d;
|
|
12
20
|
return (_d = (_c = (_b = (_a = document.fullscreenEnabled) !== null && _a !== void 0 ? _a : document.webkitFullscreenEnabled) !== null && _b !== void 0 ? _b : document.mozFullScreenEnabled) !== null && _c !== void 0 ? _c : document.msFullscreenEnabled) !== null && _d !== void 0 ? _d : false;
|
|
@@ -94,7 +102,7 @@ export const FullscreenButton = ({ auto, labels, render }) => {
|
|
|
94
102
|
return null;
|
|
95
103
|
return render.buttonFullscreen ? (React.createElement(React.Fragment, null, render.buttonFullscreen({ fullscreen, toggleFullscreen }))) : (React.createElement(IconButton, { label: fullscreen ? label(labels, "Exit Fullscreen") : label(labels, "Enter Fullscreen"), icon: fullscreen ? ExitFullscreenIcon : EnterFullscreenIcon, renderIcon: fullscreen ? render.iconExitFullscreen : render.iconEnterFullscreen, onClick: toggleFullscreen }));
|
|
96
104
|
};
|
|
97
|
-
export const Fullscreen = ({ augment }) => {
|
|
105
|
+
export const Fullscreen = ({ augment, contains, addParent }) => {
|
|
98
106
|
augment(({ toolbar: { buttons, ...restToolbar }, ...restProps }) => ({
|
|
99
107
|
toolbar: {
|
|
100
108
|
buttons: [
|
|
@@ -105,5 +113,6 @@ export const Fullscreen = ({ augment }) => {
|
|
|
105
113
|
},
|
|
106
114
|
...restProps,
|
|
107
115
|
}));
|
|
116
|
+
addParent(contains("thumbnails") ? "thumbnails" : "controller", createModule("fullscreen", FullscreenContainer));
|
|
108
117
|
};
|
|
109
118
|
export default Fullscreen;
|
package/dist/plugins/Inline.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createModule } from "../core/index.js";
|
|
|
3
3
|
export const InlineContainer = ({ inline, children }) => React.createElement("div", { ...inline }, children);
|
|
4
4
|
export const InlineModule = createModule("inline", InlineContainer);
|
|
5
5
|
export const Inline = ({ augment, replace, remove }) => {
|
|
6
|
-
augment(({ toolbar: { buttons, ...restToolbar }, open, close, controller: { focus, ...restController }, ...restProps }) => ({
|
|
6
|
+
augment(({ toolbar: { buttons, ...restToolbar }, open, close, controller: { focus, touchAction, ...restController }, ...restProps }) => ({
|
|
7
7
|
open: true,
|
|
8
8
|
close: () => { },
|
|
9
9
|
toolbar: {
|
|
@@ -11,7 +11,7 @@ export const Inline = ({ augment, replace, remove }) => {
|
|
|
11
11
|
...restToolbar,
|
|
12
12
|
},
|
|
13
13
|
inline: { style: { width: "100%", height: "100%" } },
|
|
14
|
-
controller: { focus: false, ...restController },
|
|
14
|
+
controller: { focus: false, touchAction: "pan-y", ...restController },
|
|
15
15
|
...restProps,
|
|
16
16
|
}));
|
|
17
17
|
remove("no-scroll");
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Component, LightboxProps, Plugin } from "../types.js";
|
|
3
|
+
import { ContainerRect } from "../core/index.js";
|
|
4
|
+
export declare type Position = "top" | "bottom" | "start" | "end";
|
|
5
|
+
declare module "../types.js" {
|
|
6
|
+
interface LightboxProps {
|
|
7
|
+
/** Thumbnails plugin settings */
|
|
8
|
+
thumbnails?: {
|
|
9
|
+
/** thumbnails position */
|
|
10
|
+
position?: Position;
|
|
11
|
+
/** thumbnail width */
|
|
12
|
+
width?: number;
|
|
13
|
+
/** thumbnail height */
|
|
14
|
+
height?: number;
|
|
15
|
+
/** thumbnail border width */
|
|
16
|
+
border?: number;
|
|
17
|
+
/** thumbnail border radius */
|
|
18
|
+
borderRadius?: number;
|
|
19
|
+
/** thumbnail inner padding */
|
|
20
|
+
padding?: number;
|
|
21
|
+
/** gap between thumbnails */
|
|
22
|
+
gap?: number;
|
|
23
|
+
/** `object-fit` setting */
|
|
24
|
+
imageFit?: ImageFit;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
interface Render {
|
|
28
|
+
thumbnail?: ({ slide, rect, render, imageFit, }: {
|
|
29
|
+
slide: Slide;
|
|
30
|
+
rect: ContainerRect;
|
|
31
|
+
render: Render;
|
|
32
|
+
imageFit: ImageFit;
|
|
33
|
+
}) => React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
declare type DeepNonNullable<T> = NonNullable<{
|
|
37
|
+
[K in keyof T]-?: NonNullable<T[K]>;
|
|
38
|
+
}>;
|
|
39
|
+
declare type ThumbnailsInternal = DeepNonNullable<LightboxProps["thumbnails"]>;
|
|
40
|
+
declare type ThumbnailsTrackProps = Pick<LightboxProps, "slides" | "carousel" | "animation" | "render"> & {
|
|
41
|
+
container: React.RefObject<HTMLDivElement>;
|
|
42
|
+
thumbnails: ThumbnailsInternal;
|
|
43
|
+
startingIndex: number;
|
|
44
|
+
thumbnailRect: ContainerRect;
|
|
45
|
+
};
|
|
46
|
+
export declare const ThumbnailsTrack: React.FC<ThumbnailsTrackProps>;
|
|
47
|
+
/** Thumbnails plugin component */
|
|
48
|
+
export declare const ThumbnailsComponent: Component;
|
|
49
|
+
/** Thumbnails plugin */
|
|
50
|
+
export declare const Thumbnails: Plugin;
|
|
51
|
+
export default Thumbnails;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { clsx, createIcon, createModule, cssClass, cssVar, ImageSlide, useEnhancedEffect, useEvents, useLatest, useMotionPreference, useRTL, } from "../core/index.js";
|
|
3
|
+
const defaultThumbnailsProps = {
|
|
4
|
+
position: "bottom",
|
|
5
|
+
width: 120,
|
|
6
|
+
height: 80,
|
|
7
|
+
border: 1,
|
|
8
|
+
borderRadius: 4,
|
|
9
|
+
padding: 4,
|
|
10
|
+
gap: 16,
|
|
11
|
+
imageFit: "contain",
|
|
12
|
+
};
|
|
13
|
+
const VideoThumbnailIcon = createIcon("VideoThumbnail", React.createElement("path", { d: "M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" }));
|
|
14
|
+
const UnknownThumbnailIcon = createIcon("UnknownThumbnail", React.createElement("path", { d: "M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z" }));
|
|
15
|
+
const cssPrefix = (value) => `thumbnails${value ? `_${value}` : ""}`;
|
|
16
|
+
const cssThumbnailPrefix = (value) => cssPrefix(`thumbnail${value ? `_${value}` : ""}`);
|
|
17
|
+
const getSlide = (slides, index) => slides[((index % slides.length) + slides.length) % slides.length];
|
|
18
|
+
const isHorizontal = (position) => ["top", "bottom"].includes(position);
|
|
19
|
+
const boxSize = (thumbnails, dimension, includeGap) => dimension + 2 * (thumbnails.border + thumbnails.padding) + (includeGap ? thumbnails.gap : 0);
|
|
20
|
+
const renderThumbnail = ({ slide, render, rect, imageFit }) => {
|
|
21
|
+
var _a;
|
|
22
|
+
const customThumbnail = (_a = render.thumbnail) === null || _a === void 0 ? void 0 : _a.call(render, { slide, render, rect, imageFit });
|
|
23
|
+
if (customThumbnail) {
|
|
24
|
+
return customThumbnail;
|
|
25
|
+
}
|
|
26
|
+
const thumbnailIconClass = cssClass(cssThumbnailPrefix("icon"));
|
|
27
|
+
if ("type" in slide) {
|
|
28
|
+
if (slide.type === "video") {
|
|
29
|
+
return (React.createElement(React.Fragment, null,
|
|
30
|
+
"poster" in slide && (React.createElement("img", { alt: "", src: slide.poster, className: clsx(cssClass("fullsize"), cssClass(cssPrefix("contain_image"))) })),
|
|
31
|
+
React.createElement(VideoThumbnailIcon, { className: thumbnailIconClass })));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if ("src" in slide) {
|
|
35
|
+
return React.createElement(ImageSlide, { slide: slide, render: render, rect: rect, imageFit: imageFit });
|
|
36
|
+
}
|
|
37
|
+
return React.createElement(UnknownThumbnailIcon, { className: thumbnailIconClass });
|
|
38
|
+
};
|
|
39
|
+
const Thumbnail = ({ rect, slide, onClick, active, fadeIn, fadeOut, placeholder, render, imageFit, }) => (React.createElement("button", { type: "button", className: clsx(cssClass(cssThumbnailPrefix()), active && cssClass(cssThumbnailPrefix("active")), fadeIn && cssClass(cssThumbnailPrefix("fadein")), fadeOut && cssClass(cssThumbnailPrefix("fadeout")), placeholder && cssClass(cssThumbnailPrefix("placeholder"))), style: {
|
|
40
|
+
...(fadeIn
|
|
41
|
+
? {
|
|
42
|
+
[cssVar(cssThumbnailPrefix("fadein_duration"))]: `${fadeIn.duration}ms`,
|
|
43
|
+
[cssVar(cssThumbnailPrefix("fadein_delay"))]: `${fadeIn.delay}ms`,
|
|
44
|
+
}
|
|
45
|
+
: null),
|
|
46
|
+
...(fadeOut
|
|
47
|
+
? {
|
|
48
|
+
[cssVar(cssThumbnailPrefix("fadeout_duration"))]: `${fadeOut.duration}ms`,
|
|
49
|
+
[cssVar(cssThumbnailPrefix("fadeout_delay"))]: `${fadeOut.delay}ms`,
|
|
50
|
+
}
|
|
51
|
+
: null),
|
|
52
|
+
}, onClick: onClick }, slide && renderThumbnail({ slide, render, rect, imageFit })));
|
|
53
|
+
export const ThumbnailsTrack = ({ container, startingIndex, slides, carousel, animation, render, thumbnails, thumbnailRect, }) => {
|
|
54
|
+
const track = React.useRef(null);
|
|
55
|
+
const [state, setState] = React.useState({
|
|
56
|
+
index: startingIndex,
|
|
57
|
+
offset: 0,
|
|
58
|
+
});
|
|
59
|
+
const { publish, subscribe } = useEvents();
|
|
60
|
+
const reduceMotion = useLatest(useMotionPreference());
|
|
61
|
+
const isRTL = useLatest(useRTL());
|
|
62
|
+
const refs = React.useRef({
|
|
63
|
+
state,
|
|
64
|
+
thumbnails,
|
|
65
|
+
carousel,
|
|
66
|
+
animation,
|
|
67
|
+
animationOffset: 0,
|
|
68
|
+
});
|
|
69
|
+
refs.current.state = state;
|
|
70
|
+
refs.current.thumbnails = thumbnails;
|
|
71
|
+
refs.current.carousel = carousel;
|
|
72
|
+
refs.current.animation = animation;
|
|
73
|
+
const animationRef = React.useRef();
|
|
74
|
+
React.useEffect(() => subscribe("controller-swipe", (_, event) => {
|
|
75
|
+
if (event && typeof event === "object" && "globalIndex" in event) {
|
|
76
|
+
const { current } = refs;
|
|
77
|
+
if (container.current && track.current) {
|
|
78
|
+
const containerRect = container.current.getBoundingClientRect();
|
|
79
|
+
const trackRect = track.current.getBoundingClientRect();
|
|
80
|
+
current.animationOffset = isHorizontal(refs.current.thumbnails.position)
|
|
81
|
+
? trackRect.left - (containerRect.width - trackRect.width) / 2
|
|
82
|
+
: trackRect.top - (containerRect.height - trackRect.height) / 2;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
current.animationOffset = 0;
|
|
86
|
+
}
|
|
87
|
+
current.animationOverride =
|
|
88
|
+
"animationDuration" in event
|
|
89
|
+
? event.animationDuration
|
|
90
|
+
: undefined;
|
|
91
|
+
const newIndex = event.globalIndex;
|
|
92
|
+
setState({
|
|
93
|
+
index: newIndex,
|
|
94
|
+
offset: newIndex - current.state.index,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}), [container, subscribe]);
|
|
98
|
+
useEnhancedEffect(() => {
|
|
99
|
+
var _a, _b, _c, _d;
|
|
100
|
+
if (track.current && state.offset) {
|
|
101
|
+
const { current } = refs;
|
|
102
|
+
(_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
103
|
+
const animationDuration = (_b = current.animationOverride) !== null && _b !== void 0 ? _b : current.animation.swipe;
|
|
104
|
+
animationRef.current = (_d = (_c = track.current).animate) === null || _d === void 0 ? void 0 : _d.call(_c, isHorizontal(current.thumbnails.position)
|
|
105
|
+
? [
|
|
106
|
+
{
|
|
107
|
+
transform: `translate3d(${(isRTL.current ? -1 : 1) *
|
|
108
|
+
boxSize(current.thumbnails, current.thumbnails.width, true) *
|
|
109
|
+
state.offset +
|
|
110
|
+
current.animationOffset}px, 0, 0)`,
|
|
111
|
+
},
|
|
112
|
+
{ transform: "translate3d(0, 0, 0)" },
|
|
113
|
+
]
|
|
114
|
+
: [
|
|
115
|
+
{
|
|
116
|
+
transform: `translate3d(0, ${boxSize(current.thumbnails, current.thumbnails.height, true) * state.offset +
|
|
117
|
+
current.animationOffset}px, 0)`,
|
|
118
|
+
},
|
|
119
|
+
{ transform: "translate3d(0, 0, 0)" },
|
|
120
|
+
], reduceMotion.current ? 0 : animationDuration);
|
|
121
|
+
if (animationRef.current) {
|
|
122
|
+
animationRef.current.onfinish = () => {
|
|
123
|
+
animationRef.current = undefined;
|
|
124
|
+
if (refs.current.state.index === state.index) {
|
|
125
|
+
setState({ index: state.index, offset: 0 });
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
current.animationOffset = 0;
|
|
130
|
+
}
|
|
131
|
+
}, [state.index, state.offset, isRTL, reduceMotion]);
|
|
132
|
+
const { index, offset } = state;
|
|
133
|
+
const { finite, preload } = carousel;
|
|
134
|
+
const items = [];
|
|
135
|
+
if (slides.length > 0) {
|
|
136
|
+
if (offset < 0) {
|
|
137
|
+
for (let i = index - preload + offset; i < index - preload; i += 1) {
|
|
138
|
+
items.push({ slide: null, index: i, placeholder: true });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (let i = index - preload - (offset > 0 ? offset : 0); i < index; i += 1) {
|
|
142
|
+
if (!(finite && i < 0)) {
|
|
143
|
+
items.push({ slide: getSlide(slides, i), index: i });
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
items.push({ slide: null, index: i, placeholder: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
items.push({ slide: getSlide(slides, index), index });
|
|
150
|
+
for (let i = index + 1; i <= index + preload - (offset < 0 ? offset : 0); i += 1) {
|
|
151
|
+
if (!finite || i <= slides.length - 1) {
|
|
152
|
+
items.push({ slide: getSlide(slides, i), index: i });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
items.push({ slide: null, index: i, placeholder: true });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (offset > 0) {
|
|
159
|
+
for (let i = index + preload + 1; i <= index + preload + offset; i += 1) {
|
|
160
|
+
items.push({ slide: null, index: i, placeholder: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const handleClick = (slideIndex) => () => {
|
|
165
|
+
if (slideIndex > index) {
|
|
166
|
+
publish("next", slideIndex - index);
|
|
167
|
+
}
|
|
168
|
+
else if (slideIndex < index) {
|
|
169
|
+
publish("prev", index - slideIndex);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const { width, height, border, borderRadius, padding, gap, imageFit } = thumbnails;
|
|
173
|
+
return (React.createElement("div", { className: clsx(cssClass(cssPrefix("container")), cssClass("flex_center")), style: {
|
|
174
|
+
...(width !== defaultThumbnailsProps.width
|
|
175
|
+
? { [cssVar(cssThumbnailPrefix("width"))]: `${boxSize(thumbnails, width)}px` }
|
|
176
|
+
: null),
|
|
177
|
+
...(height !== defaultThumbnailsProps.height
|
|
178
|
+
? { [cssVar(cssThumbnailPrefix("height"))]: `${boxSize(thumbnails, height)}px` }
|
|
179
|
+
: null),
|
|
180
|
+
...(border !== defaultThumbnailsProps.border
|
|
181
|
+
? { [cssVar(cssThumbnailPrefix("border"))]: `${border}px` }
|
|
182
|
+
: null),
|
|
183
|
+
...(borderRadius !== defaultThumbnailsProps.borderRadius
|
|
184
|
+
? { [cssVar(cssThumbnailPrefix("border_radius"))]: `${borderRadius}px` }
|
|
185
|
+
: null),
|
|
186
|
+
...(padding !== defaultThumbnailsProps.padding
|
|
187
|
+
? { [cssVar(cssThumbnailPrefix("padding"))]: `${padding}px` }
|
|
188
|
+
: null),
|
|
189
|
+
...(gap !== defaultThumbnailsProps.gap ? { [cssVar(cssThumbnailPrefix("gap"))]: `${gap}px` } : null),
|
|
190
|
+
} },
|
|
191
|
+
React.createElement("nav", { ref: track, className: cssClass(cssPrefix("track")) }, items.map(({ slide, index: slideIndex, placeholder }) => {
|
|
192
|
+
var _a;
|
|
193
|
+
const fadeAnimationDuration = ((_a = refs.current.animationOverride) !== null && _a !== void 0 ? _a : animation.swipe) / Math.abs(offset || 1);
|
|
194
|
+
const fadeIn = (offset > 0 && slideIndex > index + preload - offset && slideIndex <= index + preload) ||
|
|
195
|
+
(offset < 0 && slideIndex < index - preload - offset && slideIndex >= index - preload)
|
|
196
|
+
? {
|
|
197
|
+
duration: fadeAnimationDuration,
|
|
198
|
+
delay: ((offset > 0
|
|
199
|
+
? slideIndex - (index + preload - offset)
|
|
200
|
+
: index - preload - offset - slideIndex) -
|
|
201
|
+
1) *
|
|
202
|
+
fadeAnimationDuration,
|
|
203
|
+
}
|
|
204
|
+
: undefined;
|
|
205
|
+
const fadeOut = (offset > 0 && slideIndex < index - preload) || (offset < 0 && slideIndex > index + preload)
|
|
206
|
+
? {
|
|
207
|
+
duration: fadeAnimationDuration,
|
|
208
|
+
delay: (offset > 0
|
|
209
|
+
? offset - (index - preload - slideIndex)
|
|
210
|
+
: -offset - (slideIndex - (index + preload))) * fadeAnimationDuration,
|
|
211
|
+
}
|
|
212
|
+
: undefined;
|
|
213
|
+
return (React.createElement(Thumbnail, { key: slideIndex, rect: thumbnailRect, slide: slide, imageFit: imageFit, render: render, active: slideIndex === index, fadeIn: fadeIn, fadeOut: fadeOut, placeholder: Boolean(placeholder), onClick: handleClick(slideIndex) }));
|
|
214
|
+
}))));
|
|
215
|
+
};
|
|
216
|
+
export const ThumbnailsComponent = ({ thumbnails: originalThumbnails, slides, index, carousel, animation, render, children, }) => {
|
|
217
|
+
const thumbnails = { ...defaultThumbnailsProps, ...originalThumbnails };
|
|
218
|
+
const ref = React.useRef(null);
|
|
219
|
+
const track = (React.createElement(ThumbnailsTrack, { container: ref, slides: slides, thumbnails: thumbnails, carousel: carousel, animation: animation, render: render, startingIndex: index, thumbnailRect: { width: thumbnails.width, height: thumbnails.height } }));
|
|
220
|
+
return (React.createElement("div", { ref: ref, className: clsx(cssClass(cssPrefix()), cssClass(cssPrefix(`${thumbnails.position}`)), cssClass("fullsize")) },
|
|
221
|
+
(thumbnails.position === "start" || thumbnails.position === "top") && track,
|
|
222
|
+
React.createElement("div", { className: cssClass(cssPrefix("wrapper")) }, children),
|
|
223
|
+
(thumbnails.position === "end" || thumbnails.position === "bottom") && track));
|
|
224
|
+
};
|
|
225
|
+
export const Thumbnails = ({ augment, contains, append, addParent }) => {
|
|
226
|
+
augment(({ thumbnails: originalThumbnails, ...restProps }) => ({
|
|
227
|
+
thumbnails: {
|
|
228
|
+
...defaultThumbnailsProps,
|
|
229
|
+
...originalThumbnails,
|
|
230
|
+
},
|
|
231
|
+
...restProps,
|
|
232
|
+
}));
|
|
233
|
+
const module = createModule("thumbnails", ThumbnailsComponent);
|
|
234
|
+
if (contains("fullscreen")) {
|
|
235
|
+
append("fullscreen", module);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
addParent("controller", module);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
export default Thumbnails;
|
package/dist/plugins/index.d.ts
CHANGED
package/dist/plugins/index.js
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
.yarl__thumbnails {
|
|
2
|
+
display: flex;
|
|
3
|
+
}
|
|
4
|
+
.yarl__thumbnails_top, .yarl__thumbnails_bottom {
|
|
5
|
+
flex-flow: column nowrap;
|
|
6
|
+
}
|
|
7
|
+
.yarl__thumbnails_top .yarl__thumbnails_track, .yarl__thumbnails_bottom .yarl__thumbnails_track {
|
|
8
|
+
flex-flow: row nowrap;
|
|
9
|
+
}
|
|
10
|
+
.yarl__thumbnails_start, .yarl__thumbnails_end {
|
|
11
|
+
flex-flow: row nowrap;
|
|
12
|
+
}
|
|
13
|
+
.yarl__thumbnails_start .yarl__thumbnails_track, .yarl__thumbnails_end .yarl__thumbnails_track {
|
|
14
|
+
flex-flow: column nowrap;
|
|
15
|
+
}
|
|
16
|
+
.yarl__thumbnails_wrapper {
|
|
17
|
+
flex-grow: 1;
|
|
18
|
+
position: relative;
|
|
19
|
+
}
|
|
20
|
+
.yarl__thumbnails_container {
|
|
21
|
+
color: #fff;
|
|
22
|
+
background-color: #000;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
padding: 16px;
|
|
25
|
+
position: relative;
|
|
26
|
+
z-index: 0;
|
|
27
|
+
-webkit-user-select: none;
|
|
28
|
+
-moz-user-select: none;
|
|
29
|
+
-ms-user-select: none;
|
|
30
|
+
user-select: none;
|
|
31
|
+
-webkit-touch-callout: none;
|
|
32
|
+
}
|
|
33
|
+
.yarl__thumbnails_container::before, .yarl__thumbnails_container::after {
|
|
34
|
+
content: "";
|
|
35
|
+
position: absolute;
|
|
36
|
+
z-index: 1;
|
|
37
|
+
}
|
|
38
|
+
.yarl__thumbnails_top .yarl__thumbnails_container::before, .yarl__thumbnails_bottom .yarl__thumbnails_container::before {
|
|
39
|
+
left: 0;
|
|
40
|
+
background: linear-gradient(to left, transparent, #000);
|
|
41
|
+
}
|
|
42
|
+
.yarl__thumbnails_top .yarl__thumbnails_container::after, .yarl__thumbnails_bottom .yarl__thumbnails_container::after {
|
|
43
|
+
right: 0;
|
|
44
|
+
background: linear-gradient(to right, transparent, #000);
|
|
45
|
+
}
|
|
46
|
+
.yarl__thumbnails_top .yarl__thumbnails_container::before, .yarl__thumbnails_top .yarl__thumbnails_container::after, .yarl__thumbnails_bottom .yarl__thumbnails_container::before, .yarl__thumbnails_bottom .yarl__thumbnails_container::after {
|
|
47
|
+
top: 0;
|
|
48
|
+
bottom: 0;
|
|
49
|
+
width: 60px;
|
|
50
|
+
}
|
|
51
|
+
.yarl__thumbnails_start .yarl__thumbnails_container::before, .yarl__thumbnails_end .yarl__thumbnails_container::before {
|
|
52
|
+
top: 0;
|
|
53
|
+
background: linear-gradient(to top, transparent, #000);
|
|
54
|
+
}
|
|
55
|
+
.yarl__thumbnails_start .yarl__thumbnails_container::after, .yarl__thumbnails_end .yarl__thumbnails_container::after {
|
|
56
|
+
bottom: 0;
|
|
57
|
+
background: linear-gradient(to bottom, transparent, #000);
|
|
58
|
+
}
|
|
59
|
+
.yarl__thumbnails_start .yarl__thumbnails_container::before, .yarl__thumbnails_start .yarl__thumbnails_container::after, .yarl__thumbnails_end .yarl__thumbnails_container::before, .yarl__thumbnails_end .yarl__thumbnails_container::after {
|
|
60
|
+
left: 0;
|
|
61
|
+
right: 0;
|
|
62
|
+
height: 60px;
|
|
63
|
+
}
|
|
64
|
+
.yarl__thumbnails_track {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: var(--yarl__thumbnails_thumbnail_gap, 16px);
|
|
67
|
+
}
|
|
68
|
+
.yarl__thumbnails_thumbnail {
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
-webkit-appearance: none;
|
|
71
|
+
-moz-appearance: none;
|
|
72
|
+
appearance: none;
|
|
73
|
+
background: transparent;
|
|
74
|
+
border: var(--yarl__thumbnails_thumbnail_border, 1px) solid rgba(255, 255, 255, 0.8);
|
|
75
|
+
border-radius: var(--yarl__thumbnails_thumbnail_border_radius, 4px);
|
|
76
|
+
-webkit-tap-highlight-color: transparent;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
position: relative;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
padding: var(--yarl__thumbnails_thumbnail_padding, 4px);
|
|
81
|
+
width: var(--yarl__thumbnails_thumbnail_width, 120px);
|
|
82
|
+
height: var(--yarl__thumbnails_thumbnail_height, 80px);
|
|
83
|
+
box-sizing: content-box;
|
|
84
|
+
}
|
|
85
|
+
.yarl__thumbnails_thumbnail_active {
|
|
86
|
+
border-color: #fff;
|
|
87
|
+
}
|
|
88
|
+
.yarl__thumbnails_thumbnail_fadein {
|
|
89
|
+
opacity: 0;
|
|
90
|
+
-webkit-animation: yarl__thumbnails_thumbnail_fadein var(--yarl__thumbnails_thumbnail_fadein_duration, 0.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadein_delay, 0s) forwards;
|
|
91
|
+
animation: yarl__thumbnails_thumbnail_fadein var(--yarl__thumbnails_thumbnail_fadein_duration, 0.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadein_delay, 0s) forwards;
|
|
92
|
+
}
|
|
93
|
+
.yarl__thumbnails_thumbnail_fadeout {
|
|
94
|
+
-webkit-animation: yarl__thumbnails_thumbnail_fadeout var(--yarl__thumbnails_thumbnail_fadeout_duration, 0.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadeout_delay, 0s) forwards;
|
|
95
|
+
animation: yarl__thumbnails_thumbnail_fadeout var(--yarl__thumbnails_thumbnail_fadeout_duration, 0.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadeout_delay, 0s) forwards;
|
|
96
|
+
cursor: unset;
|
|
97
|
+
}
|
|
98
|
+
.yarl__thumbnails_thumbnail_placeholder {
|
|
99
|
+
visibility: hidden;
|
|
100
|
+
cursor: unset;
|
|
101
|
+
}
|
|
102
|
+
.yarl__thumbnails_thumbnail_icon {
|
|
103
|
+
color: rgba(255, 255, 255, 0.8);
|
|
104
|
+
-webkit-filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.8));
|
|
105
|
+
filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.8));
|
|
106
|
+
position: absolute;
|
|
107
|
+
top: 50%;
|
|
108
|
+
left: 50%;
|
|
109
|
+
-webkit-transform: translate3d(-50%, -50%, 0);
|
|
110
|
+
transform: translate3d(-50%, -50%, 0);
|
|
111
|
+
width: 32px;
|
|
112
|
+
height: 32px;
|
|
113
|
+
}
|
|
114
|
+
.yarl__thumbnails_contain_image {
|
|
115
|
+
-o-object-fit: contain;
|
|
116
|
+
object-fit: contain;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@-webkit-keyframes yarl__thumbnails_thumbnail_fadein {
|
|
120
|
+
from {
|
|
121
|
+
opacity: 0;
|
|
122
|
+
}
|
|
123
|
+
to {
|
|
124
|
+
opacity: 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes yarl__thumbnails_thumbnail_fadein {
|
|
129
|
+
from {
|
|
130
|
+
opacity: 0;
|
|
131
|
+
}
|
|
132
|
+
to {
|
|
133
|
+
opacity: 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
@-webkit-keyframes yarl__thumbnails_thumbnail_fadeout {
|
|
137
|
+
from {
|
|
138
|
+
opacity: 1;
|
|
139
|
+
}
|
|
140
|
+
to {
|
|
141
|
+
opacity: 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
@keyframes yarl__thumbnails_thumbnail_fadeout {
|
|
145
|
+
from {
|
|
146
|
+
opacity: 1;
|
|
147
|
+
}
|
|
148
|
+
to {
|
|
149
|
+
opacity: 0;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
.yarl__fullsize {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
1
5
|
.yarl__portal {
|
|
2
6
|
position: fixed;
|
|
3
7
|
inset: 0;
|
|
@@ -9,16 +13,14 @@
|
|
|
9
13
|
.yarl__portal_open {
|
|
10
14
|
opacity: 1;
|
|
11
15
|
}
|
|
12
|
-
.yarl__container
|
|
16
|
+
.yarl__container {
|
|
13
17
|
-webkit-user-select: none;
|
|
14
18
|
-moz-user-select: none;
|
|
15
19
|
-ms-user-select: none;
|
|
16
20
|
user-select: none;
|
|
17
|
-
touch-action:
|
|
21
|
+
touch-action: var(--yarl__controller_touch_action, none);
|
|
18
22
|
}
|
|
19
23
|
.yarl__container {
|
|
20
|
-
width: 100%;
|
|
21
|
-
height: 100%;
|
|
22
24
|
position: relative;
|
|
23
25
|
overflow: hidden;
|
|
24
26
|
color: #fff;
|
|
@@ -56,8 +58,6 @@
|
|
|
56
58
|
transition: unset;
|
|
57
59
|
}
|
|
58
60
|
.yarl__slide_image {
|
|
59
|
-
width: 100%;
|
|
60
|
-
height: 100%;
|
|
61
61
|
-o-object-fit: contain;
|
|
62
62
|
object-fit: contain;
|
|
63
63
|
-moz-user-select: none;
|
|
@@ -144,7 +144,6 @@
|
|
|
144
144
|
display: flex;
|
|
145
145
|
justify-content: flex-end;
|
|
146
146
|
padding: 8px;
|
|
147
|
-
gap: 8px;
|
|
148
147
|
}
|
|
149
148
|
[dir=rtl] .yarl__toolbar {
|
|
150
149
|
inset: 0 auto auto 0;
|
package/dist/types.d.ts
CHANGED
|
@@ -33,10 +33,10 @@ export interface CarouselSettings {
|
|
|
33
33
|
finite: boolean;
|
|
34
34
|
/** the lightbox preloads (2 * preload + 1) slides */
|
|
35
35
|
preload: number;
|
|
36
|
-
/** padding around each slide */
|
|
37
|
-
padding: string |
|
|
36
|
+
/** padding around each slide (e.g., "16px", "10px 20px" or 0) */
|
|
37
|
+
padding: string | 0;
|
|
38
38
|
/** spacing between slides (e.g., "100px", "50%" or 0) */
|
|
39
|
-
spacing: string |
|
|
39
|
+
spacing: string | 0;
|
|
40
40
|
/** `object-fit` setting for image slides */
|
|
41
41
|
imageFit: ImageFit;
|
|
42
42
|
}
|
|
@@ -51,6 +51,8 @@ export interface AnimationSettings {
|
|
|
51
51
|
export interface ControllerSettings {
|
|
52
52
|
/** if true, the lightbox captures focus when it opens */
|
|
53
53
|
focus: boolean;
|
|
54
|
+
/** controller `touch-action` */
|
|
55
|
+
touchAction: "none" | "pan-y";
|
|
54
56
|
}
|
|
55
57
|
/** Custom render functions. */
|
|
56
58
|
export interface Render {
|
|
@@ -173,21 +175,25 @@ export declare type Node = {
|
|
|
173
175
|
export declare type Augmentation = (props: LightboxProps) => LightboxProps;
|
|
174
176
|
/** Plugin methods */
|
|
175
177
|
export declare type PluginMethods = {
|
|
176
|
-
/**
|
|
178
|
+
/** test if a target module is present */
|
|
179
|
+
contains: (target: string) => boolean;
|
|
180
|
+
/** add module as a parent */
|
|
177
181
|
addParent: (target: string, module: Module) => void;
|
|
178
|
-
/** add module as child */
|
|
182
|
+
/** add module as a child */
|
|
179
183
|
addChild: (target: string, module: Module, precede?: boolean) => void;
|
|
180
|
-
/** add module as sibling */
|
|
184
|
+
/** add module as a sibling */
|
|
181
185
|
addSibling: (target: string, module: Module, precede?: boolean) => void;
|
|
182
186
|
/** replace module */
|
|
183
187
|
replace: (target: string, module: Module) => void;
|
|
188
|
+
/** add module as a child and inherit all existing children */
|
|
189
|
+
append: (target: string, module: Module) => void;
|
|
184
190
|
/** remove module */
|
|
185
191
|
remove: (target: string) => void;
|
|
186
192
|
/** augment lightbox props */
|
|
187
193
|
augment: (augmentation: Augmentation) => void;
|
|
188
194
|
};
|
|
189
195
|
/** Lightbox plugin */
|
|
190
|
-
export declare type Plugin = ({ addParent, addChild, addSibling, replace, remove, augment }: PluginMethods) => void;
|
|
196
|
+
export declare type Plugin = ({ addParent, addChild, addSibling, replace, remove, append, augment }: PluginMethods) => void;
|
|
191
197
|
/** Deep partial utility type */
|
|
192
198
|
export declare type DeepPartial<T, K extends keyof T> = Omit<T, K> & {
|
|
193
199
|
[P in keyof Pick<T, K>]?: Partial<Pick<T, K>[P]>;
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yet-another-react-lightbox",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Modern React lightbox component",
|
|
5
5
|
"author": "Igor Danchenko",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"./plugins/fullscreen": "./dist/plugins/Fullscreen.js",
|
|
16
16
|
"./plugins/inline": "./dist/plugins/Inline.js",
|
|
17
17
|
"./plugins/slideshow": "./dist/plugins/Slideshow.js",
|
|
18
|
+
"./plugins/thumbnails": "./dist/plugins/Thumbnails.js",
|
|
19
|
+
"./plugins/thumbnails.css": "./dist/plugins/thumbnails.css",
|
|
18
20
|
"./plugins/video": "./dist/plugins/Video.js",
|
|
19
21
|
"./styles.css": "./dist/styles.css"
|
|
20
22
|
},
|
|
@@ -42,6 +44,9 @@
|
|
|
42
44
|
"plugins/slideshow": [
|
|
43
45
|
"dist/plugins/Slideshow.d.ts"
|
|
44
46
|
],
|
|
47
|
+
"plugins/thumbnails": [
|
|
48
|
+
"dist/plugins/Thumbnails.d.ts"
|
|
49
|
+
],
|
|
45
50
|
"plugins/video": [
|
|
46
51
|
"dist/plugins/Video.d.ts"
|
|
47
52
|
]
|
|
@@ -85,14 +90,14 @@
|
|
|
85
90
|
"@semantic-release/github": "^8.0.4",
|
|
86
91
|
"@testing-library/jest-dom": "^5.16.4",
|
|
87
92
|
"@testing-library/react": "^13.3.0",
|
|
88
|
-
"@testing-library/user-event": "^14.2.
|
|
89
|
-
"@types/jest": "^28.1.
|
|
90
|
-
"@types/react": "^18.0.
|
|
93
|
+
"@testing-library/user-event": "^14.2.1",
|
|
94
|
+
"@types/jest": "^28.1.2",
|
|
95
|
+
"@types/react": "^18.0.14",
|
|
91
96
|
"@types/react-dom": "^18.0.5",
|
|
92
97
|
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
|
93
98
|
"@typescript-eslint/parser": "^5.28.0",
|
|
94
99
|
"autoprefixer": "^10.4.7",
|
|
95
|
-
"eslint": "^8.
|
|
100
|
+
"eslint": "^8.18.0",
|
|
96
101
|
"eslint-config-airbnb": "^19.0.4",
|
|
97
102
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
|
98
103
|
"eslint-config-prettier": "^8.5.0",
|
|
@@ -104,17 +109,17 @@
|
|
|
104
109
|
"husky": "^8.0.1",
|
|
105
110
|
"jest": "^28.1.1",
|
|
106
111
|
"jest-environment-jsdom": "^28.1.1",
|
|
107
|
-
"lint-staged": "^13.0.
|
|
112
|
+
"lint-staged": "^13.0.2",
|
|
108
113
|
"npm-run-all": "^4.1.5",
|
|
109
114
|
"postcss": "^8.4.14",
|
|
110
115
|
"postcss-cli": "^9.1.0",
|
|
111
|
-
"prettier": "^2.7.
|
|
116
|
+
"prettier": "^2.7.1",
|
|
112
117
|
"react": "^18.2.0",
|
|
113
118
|
"react-dom": "^18.2.0",
|
|
114
119
|
"rimraf": "^3.0.2",
|
|
115
120
|
"sass": "^1.52.3",
|
|
116
121
|
"ts-jest": "^28.0.5",
|
|
117
|
-
"typescript": "^4.7.
|
|
122
|
+
"typescript": "^4.7.4"
|
|
118
123
|
},
|
|
119
124
|
"keywords": [
|
|
120
125
|
"react",
|