yet-another-react-lightbox 1.0.0
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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/Lightbox.d.ts +49 -0
- package/dist/Lightbox.js +29 -0
- package/dist/core/components/IconButton.d.ts +6 -0
- package/dist/core/components/IconButton.js +4 -0
- package/dist/core/components/Icons.d.ts +25 -0
- package/dist/core/components/Icons.js +14 -0
- package/dist/core/components/ImageSlide.d.ts +5 -0
- package/dist/core/components/ImageSlide.js +60 -0
- package/dist/core/components/index.d.ts +3 -0
- package/dist/core/components/index.js +3 -0
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.js +70 -0
- package/dist/core/contexts/Events.d.ts +16 -0
- package/dist/core/contexts/Events.js +31 -0
- package/dist/core/contexts/Timeouts.d.ts +8 -0
- package/dist/core/contexts/Timeouts.js +35 -0
- package/dist/core/contexts/index.d.ts +2 -0
- package/dist/core/contexts/index.js +2 -0
- package/dist/core/hooks/index.d.ts +4 -0
- package/dist/core/hooks/index.js +4 -0
- package/dist/core/hooks/useContainerRect.d.ts +10 -0
- package/dist/core/hooks/useContainerRect.js +33 -0
- package/dist/core/hooks/useEnhancedEffect.d.ts +2 -0
- package/dist/core/hooks/useEnhancedEffect.js +2 -0
- package/dist/core/hooks/useLatest.d.ts +2 -0
- package/dist/core/hooks/useLatest.js +6 -0
- package/dist/core/hooks/useSensors.d.ts +15 -0
- package/dist/core/hooks/useSensors.js +37 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/modules/Carousel.d.ts +3 -0
- package/dist/core/modules/Carousel.js +34 -0
- package/dist/core/modules/Controller.d.ts +13 -0
- package/dist/core/modules/Controller.js +267 -0
- package/dist/core/modules/Core.d.ts +3 -0
- package/dist/core/modules/Core.js +6 -0
- package/dist/core/modules/Navigation.d.ts +14 -0
- package/dist/core/modules/Navigation.js +25 -0
- package/dist/core/modules/NoScroll.d.ts +3 -0
- package/dist/core/modules/NoScroll.js +22 -0
- package/dist/core/modules/Portal.d.ts +3 -0
- package/dist/core/modules/Portal.js +34 -0
- package/dist/core/modules/Toolbar.d.ts +3 -0
- package/dist/core/modules/Toolbar.js +10 -0
- package/dist/core/modules/index.d.ts +7 -0
- package/dist/core/modules/index.js +7 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/plugins/Fullscreen.d.ts +30 -0
- package/dist/plugins/Fullscreen.js +108 -0
- package/dist/plugins/Inline.d.ts +10 -0
- package/dist/plugins/Inline.js +17 -0
- package/dist/plugins/Video.d.ts +21 -0
- package/dist/plugins/Video.js +44 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.js +3 -0
- package/dist/styles.css +237 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +61 -0
- package/package.json +105 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { LightboxDefaultProps } from "../../types.js";
|
|
3
|
+
import { createModule } from "../config.js";
|
|
4
|
+
import { clsx, cssClass, cssVar } from "../utils.js";
|
|
5
|
+
import { ImageSlide } from "../components/index.js";
|
|
6
|
+
import { useController } from "./Controller.js";
|
|
7
|
+
const CarouselSlide = ({ slide, offset, renderSlide }) => (React.createElement("div", { className: clsx(cssClass("slide"), cssClass("flex_center")), style: { [cssVar("slide_offset")]: offset } }, renderSlide(slide) || ("src" in slide && React.createElement(ImageSlide, { slide: slide }))));
|
|
8
|
+
export const Carousel = (props) => {
|
|
9
|
+
const { slides, carousel: { finite, preload, padding, spacing }, render: { slide: renderSlide }, } = props;
|
|
10
|
+
const { currentIndex, globalIndex } = useController();
|
|
11
|
+
const items = [];
|
|
12
|
+
if (slides?.length > 0) {
|
|
13
|
+
for (let i = currentIndex - preload; i < currentIndex; i += 1) {
|
|
14
|
+
if (!finite || i >= 0) {
|
|
15
|
+
items.push(React.createElement(CarouselSlide, { key: globalIndex + i - currentIndex, slide: slides[(i + preload * slides.length) % slides.length], offset: i - currentIndex, renderSlide: renderSlide }));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
items.push(React.createElement(CarouselSlide, { key: globalIndex, slide: slides[currentIndex], offset: 0, renderSlide: renderSlide }));
|
|
19
|
+
for (let i = currentIndex + 1; i <= currentIndex + preload; i += 1) {
|
|
20
|
+
if (!finite || i <= slides.length - 1) {
|
|
21
|
+
items.push(React.createElement(CarouselSlide, { key: globalIndex + i - currentIndex, slide: slides[i % slides.length], offset: i - currentIndex, renderSlide: renderSlide }));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return (React.createElement("div", { className: cssClass("carousel"), style: {
|
|
26
|
+
...(padding !== LightboxDefaultProps.carousel.padding
|
|
27
|
+
? { [cssVar("carousel_padding")]: padding }
|
|
28
|
+
: null),
|
|
29
|
+
...(spacing !== LightboxDefaultProps.carousel.spacing
|
|
30
|
+
? { [cssVar("carousel_spacing")]: spacing !== 0 ? spacing : "0px" }
|
|
31
|
+
: null),
|
|
32
|
+
} }, items));
|
|
33
|
+
};
|
|
34
|
+
export const CarouselModule = createModule("carousel", Carousel);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Component } from "../../types.js";
|
|
3
|
+
import { ContainerRect, SubscribeSensors } from "../hooks/index.js";
|
|
4
|
+
export declare type ControllerContextType = {
|
|
5
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
6
|
+
containerRect: ContainerRect;
|
|
7
|
+
currentIndex: number;
|
|
8
|
+
globalIndex: number;
|
|
9
|
+
subscribeSensors: SubscribeSensors<HTMLDivElement>;
|
|
10
|
+
};
|
|
11
|
+
export declare const useController: () => ControllerContextType;
|
|
12
|
+
export declare const Controller: Component;
|
|
13
|
+
export declare const ControllerModule: import("../../types.js").Module;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { LightboxDefaultProps } from "../../types.js";
|
|
3
|
+
import { cleanup, clsx, cssClass, cssVar, makeUseContext } from "../utils.js";
|
|
4
|
+
import { createModule } from "../config.js";
|
|
5
|
+
import { useContainerRect, useEnhancedEffect, useSensors } from "../hooks/index.js";
|
|
6
|
+
import { useEvents, useTimeouts } from "../contexts/index.js";
|
|
7
|
+
const SWIPE_OFFSET_THRESHOLD = 30;
|
|
8
|
+
const ControllerContext = React.createContext(null);
|
|
9
|
+
export const useController = makeUseContext("useController", "ControllerContext", ControllerContext);
|
|
10
|
+
export const Controller = ({ children, ...props }) => {
|
|
11
|
+
const { containerRef, setContainerRef, containerRect } = useContainerRect();
|
|
12
|
+
const { registerSensors, subscribeSensors } = useSensors();
|
|
13
|
+
const { subscribe, publish } = useEvents();
|
|
14
|
+
const { setTimeout, clearTimeout } = useTimeouts();
|
|
15
|
+
const [state, setState] = React.useState({
|
|
16
|
+
currentIndex: props.index,
|
|
17
|
+
globalIndex: props.index,
|
|
18
|
+
});
|
|
19
|
+
const refs = React.useRef({
|
|
20
|
+
state,
|
|
21
|
+
props,
|
|
22
|
+
swipeOffset: 0,
|
|
23
|
+
swipeIntent: 0,
|
|
24
|
+
swipeAnimationDuration: props.animation.swipe,
|
|
25
|
+
wheelResidualMomentum: 0,
|
|
26
|
+
pointers: [],
|
|
27
|
+
});
|
|
28
|
+
refs.current.state = state;
|
|
29
|
+
refs.current.props = props;
|
|
30
|
+
refs.current.containerRect = containerRect;
|
|
31
|
+
useEnhancedEffect(() => {
|
|
32
|
+
const preventDefault = (event) => event.preventDefault();
|
|
33
|
+
const node = containerRef.current;
|
|
34
|
+
if (node) {
|
|
35
|
+
node.addEventListener("wheel", preventDefault, { passive: false });
|
|
36
|
+
}
|
|
37
|
+
return () => {
|
|
38
|
+
if (node) {
|
|
39
|
+
node.removeEventListener("wheel", preventDefault);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}, [containerRef]);
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
containerRef.current?.focus();
|
|
45
|
+
}, [containerRef]);
|
|
46
|
+
React.useEffect(() => subscribe("close", () => {
|
|
47
|
+
setTimeout(refs.current.props.close, refs.current.props.animation.fade);
|
|
48
|
+
}), [subscribe, setTimeout]);
|
|
49
|
+
const updateSwipeOffset = React.useCallback(() => {
|
|
50
|
+
const offsetVar = cssVar("swipe_offset");
|
|
51
|
+
if (refs.current.swipeOffset !== 0) {
|
|
52
|
+
containerRef.current?.style.setProperty(offsetVar, `${Math.round(refs.current.swipeOffset)}px`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
containerRef.current?.style.removeProperty(offsetVar);
|
|
56
|
+
}
|
|
57
|
+
}, [containerRef]);
|
|
58
|
+
useEnhancedEffect(() => {
|
|
59
|
+
updateSwipeOffset();
|
|
60
|
+
});
|
|
61
|
+
const rerender = React.useCallback(() => {
|
|
62
|
+
setState((prev) => ({ ...prev }));
|
|
63
|
+
}, []);
|
|
64
|
+
const resetSwipe = React.useCallback(() => {
|
|
65
|
+
const { current } = refs;
|
|
66
|
+
current.swipeOffset = 0;
|
|
67
|
+
current.swipeIntent = 0;
|
|
68
|
+
current.swipeStartTime = undefined;
|
|
69
|
+
clearTimeout(current.swipeResetCleanup);
|
|
70
|
+
current.swipeResetCleanup = undefined;
|
|
71
|
+
clearTimeout(current.swipeIntentCleanup);
|
|
72
|
+
current.swipeIntentCleanup = undefined;
|
|
73
|
+
}, [clearTimeout]);
|
|
74
|
+
const isSwipeValid = React.useCallback((offset) => {
|
|
75
|
+
const { state: { currentIndex }, props: { carousel, slides }, } = refs.current;
|
|
76
|
+
return !(carousel.finite &&
|
|
77
|
+
((offset > 0 && currentIndex === 0) || (offset < 0 && currentIndex === slides.length - 1)));
|
|
78
|
+
}, []);
|
|
79
|
+
const swipe = React.useCallback((direction) => {
|
|
80
|
+
const { current } = refs;
|
|
81
|
+
const slidesCount = current.props.slides.length;
|
|
82
|
+
const swipeAnimationDuration = current.props.animation.swipe;
|
|
83
|
+
const { currentIndex, globalIndex } = current.state;
|
|
84
|
+
const { swipeOffset } = current;
|
|
85
|
+
let newSwipeState = "swipe-animation";
|
|
86
|
+
let newSwipeAnimationDuration = swipeAnimationDuration;
|
|
87
|
+
if (!direction) {
|
|
88
|
+
const containerWidth = current.containerRect?.width;
|
|
89
|
+
const elapsedTime = current.swipeStartTime ? Date.now() - current.swipeStartTime : 0;
|
|
90
|
+
const expectedTime = containerWidth
|
|
91
|
+
? (swipeAnimationDuration / containerWidth) * Math.abs(swipeOffset)
|
|
92
|
+
: swipeAnimationDuration;
|
|
93
|
+
if (containerWidth &&
|
|
94
|
+
((swipeOffset !== 0 && elapsedTime < swipeAnimationDuration) ||
|
|
95
|
+
Math.abs(swipeOffset) > 0.5 * containerWidth)) {
|
|
96
|
+
newSwipeAnimationDuration =
|
|
97
|
+
(swipeAnimationDuration / containerWidth) * (containerWidth - Math.abs(swipeOffset));
|
|
98
|
+
if (elapsedTime < expectedTime) {
|
|
99
|
+
newSwipeAnimationDuration =
|
|
100
|
+
(newSwipeAnimationDuration / expectedTime) * Math.max(elapsedTime, expectedTime / 5);
|
|
101
|
+
}
|
|
102
|
+
direction = swipeOffset > 0 ? "prev" : "next";
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
newSwipeAnimationDuration = swipeAnimationDuration / 2;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const newState = {};
|
|
109
|
+
if (direction === "prev") {
|
|
110
|
+
if (isSwipeValid(swipeOffset)) {
|
|
111
|
+
newState.currentIndex = (currentIndex - 1 + slidesCount) % slidesCount;
|
|
112
|
+
newState.globalIndex = globalIndex - 1;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
newSwipeState = undefined;
|
|
116
|
+
newSwipeAnimationDuration = swipeAnimationDuration;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (direction === "next") {
|
|
120
|
+
if (isSwipeValid(swipeOffset)) {
|
|
121
|
+
newState.currentIndex = (currentIndex + 1) % slidesCount;
|
|
122
|
+
newState.globalIndex = globalIndex + 1;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
newSwipeState = undefined;
|
|
126
|
+
newSwipeAnimationDuration = swipeAnimationDuration;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
resetSwipe();
|
|
130
|
+
current.swipeState = newSwipeState;
|
|
131
|
+
current.swipeAnimationDuration = newSwipeAnimationDuration;
|
|
132
|
+
if (newSwipeState) {
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
current.swipeState = undefined;
|
|
135
|
+
current.swipeAnimationDuration = current.props.animation.swipe;
|
|
136
|
+
rerender();
|
|
137
|
+
}, newSwipeAnimationDuration);
|
|
138
|
+
}
|
|
139
|
+
setState((prev) => ({ ...prev, ...newState }));
|
|
140
|
+
}, [setTimeout, resetSwipe, isSwipeValid, rerender]);
|
|
141
|
+
React.useEffect(() => cleanup(subscribe("prev", () => swipe("prev")), subscribe("next", () => swipe("next"))), [subscribe, swipe]);
|
|
142
|
+
React.useEffect(() => subscribeSensors("onKeyUp", (event) => {
|
|
143
|
+
if (event.code === "Escape") {
|
|
144
|
+
publish("close");
|
|
145
|
+
}
|
|
146
|
+
}), [subscribeSensors, publish]);
|
|
147
|
+
const clearPointer = React.useCallback((event) => {
|
|
148
|
+
const { current } = refs;
|
|
149
|
+
if (current.activePointer === event.pointerId) {
|
|
150
|
+
current.activePointer = undefined;
|
|
151
|
+
}
|
|
152
|
+
current.pointers.splice(0, current.pointers.length, ...current.pointers.filter((p) => p.pointerId !== event.pointerId));
|
|
153
|
+
}, []);
|
|
154
|
+
const addPointer = React.useCallback((event) => {
|
|
155
|
+
clearPointer(event);
|
|
156
|
+
refs.current.pointers.push(event);
|
|
157
|
+
}, [clearPointer]);
|
|
158
|
+
const onPointerDown = React.useCallback((event) => {
|
|
159
|
+
addPointer(event);
|
|
160
|
+
}, [addPointer]);
|
|
161
|
+
const onPointerMove = React.useCallback((event) => {
|
|
162
|
+
const { current } = refs;
|
|
163
|
+
const original = current.pointers.find((p) => p.pointerId === event.pointerId);
|
|
164
|
+
if (original) {
|
|
165
|
+
const deltaX = event.clientX - original.clientX;
|
|
166
|
+
const deltaY = event.clientY - original.clientY;
|
|
167
|
+
if (!current.swipeState) {
|
|
168
|
+
if (isSwipeValid(deltaX) &&
|
|
169
|
+
Math.abs(deltaX) > Math.abs(deltaY) &&
|
|
170
|
+
Math.abs(deltaX) > SWIPE_OFFSET_THRESHOLD) {
|
|
171
|
+
addPointer(event);
|
|
172
|
+
current.activePointer = event.pointerId;
|
|
173
|
+
current.swipeStartTime = Date.now();
|
|
174
|
+
current.swipeState = "swipe";
|
|
175
|
+
rerender();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (current.swipeState === "swipe") {
|
|
179
|
+
if (event.pointerId === current.activePointer) {
|
|
180
|
+
current.swipeOffset = deltaX;
|
|
181
|
+
updateSwipeOffset();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}, [addPointer, updateSwipeOffset, isSwipeValid, rerender]);
|
|
186
|
+
const onPointerUp = React.useCallback((event) => {
|
|
187
|
+
const { current } = refs;
|
|
188
|
+
if (current.pointers.find((p) => p.pointerId === event.pointerId) &&
|
|
189
|
+
current.swipeState === "swipe" &&
|
|
190
|
+
current.activePointer === event.pointerId) {
|
|
191
|
+
swipe();
|
|
192
|
+
}
|
|
193
|
+
clearPointer(event);
|
|
194
|
+
}, [clearPointer, swipe]);
|
|
195
|
+
React.useEffect(() => cleanup(subscribeSensors("onPointerDown", onPointerDown), subscribeSensors("onPointerMove", onPointerMove), subscribeSensors("onPointerUp", onPointerUp), subscribeSensors("onPointerLeave", onPointerUp), subscribeSensors("onPointerCancel", onPointerUp)), [subscribeSensors, onPointerDown, onPointerMove, onPointerUp]);
|
|
196
|
+
const onWheel = React.useCallback((event) => {
|
|
197
|
+
if (event.ctrlKey) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const { current } = refs;
|
|
204
|
+
if (!current.swipeState) {
|
|
205
|
+
if (Math.abs(event.deltaX) <= 1.2 * Math.abs(current.wheelResidualMomentum)) {
|
|
206
|
+
current.wheelResidualMomentum = event.deltaX;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
current.swipeIntent += event.deltaX;
|
|
210
|
+
clearTimeout(current.swipeIntentCleanup);
|
|
211
|
+
if (Math.abs(current.swipeIntent) > SWIPE_OFFSET_THRESHOLD) {
|
|
212
|
+
current.swipeStartTime = Date.now();
|
|
213
|
+
current.swipeIntent = 0;
|
|
214
|
+
current.wheelResidualMomentum = 0;
|
|
215
|
+
current.swipeState = "swipe";
|
|
216
|
+
rerender();
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
current.swipeIntentCleanup = setTimeout(() => {
|
|
220
|
+
current.swipeIntent = 0;
|
|
221
|
+
current.swipeIntentCleanup = undefined;
|
|
222
|
+
}, current.props.animation.swipe);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else if (current.swipeState === "swipe") {
|
|
226
|
+
const containerWidth = current.containerRect?.width;
|
|
227
|
+
if (containerWidth) {
|
|
228
|
+
current.swipeOffset -= event.deltaX;
|
|
229
|
+
current.swipeOffset =
|
|
230
|
+
Math.min(Math.abs(current.swipeOffset), containerWidth) * Math.sign(current.swipeOffset);
|
|
231
|
+
updateSwipeOffset();
|
|
232
|
+
clearTimeout(current.swipeResetCleanup);
|
|
233
|
+
if (Math.abs(current.swipeOffset) > 0.2 * containerWidth) {
|
|
234
|
+
current.wheelResidualMomentum = event.deltaX;
|
|
235
|
+
swipe();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const currentSwipeOffset = current.swipeOffset;
|
|
239
|
+
current.swipeResetCleanup = setTimeout(() => {
|
|
240
|
+
current.swipeResetCleanup = undefined;
|
|
241
|
+
if (current.swipeState === "swipe" && current.swipeOffset === currentSwipeOffset) {
|
|
242
|
+
resetSwipe();
|
|
243
|
+
current.swipeState = undefined;
|
|
244
|
+
rerender();
|
|
245
|
+
}
|
|
246
|
+
}, 2 * current.props.animation.swipe);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
current.wheelResidualMomentum = event.deltaX;
|
|
251
|
+
}
|
|
252
|
+
}, [updateSwipeOffset, setTimeout, clearTimeout, swipe, resetSwipe, rerender]);
|
|
253
|
+
React.useEffect(() => subscribeSensors("onWheel", onWheel), [subscribeSensors, onWheel]);
|
|
254
|
+
const context = React.useMemo(() => ({
|
|
255
|
+
containerRef,
|
|
256
|
+
containerRect,
|
|
257
|
+
currentIndex: state.currentIndex,
|
|
258
|
+
globalIndex: state.globalIndex,
|
|
259
|
+
subscribeSensors,
|
|
260
|
+
}), [containerRef, containerRect, state.currentIndex, state.globalIndex, subscribeSensors]);
|
|
261
|
+
return (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass("container"), refs.current.swipeState === "swipe" && cssClass("container_swipe")), style: refs.current.swipeAnimationDuration !== LightboxDefaultProps.animation.swipe
|
|
262
|
+
? {
|
|
263
|
+
[cssVar("swipe_animation_duration")]: `${Math.round(refs.current.swipeAnimationDuration)}ms`,
|
|
264
|
+
}
|
|
265
|
+
: undefined, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context }, children))));
|
|
266
|
+
};
|
|
267
|
+
export const ControllerModule = createModule("controller", Controller);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createModule } from "../config.js";
|
|
3
|
+
import { EventsProvider, TimeoutsProvider } from "../contexts/index.js";
|
|
4
|
+
export const Core = ({ children }) => (React.createElement(TimeoutsProvider, null,
|
|
5
|
+
React.createElement(EventsProvider, null, children)));
|
|
6
|
+
export const CoreModule = createModule("core", Core);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Component, Labels } from "../../types.js";
|
|
3
|
+
import { Publish } from "../contexts/index.js";
|
|
4
|
+
export declare type NavigationButtonProps = {
|
|
5
|
+
publish: Publish;
|
|
6
|
+
labels: Labels | undefined;
|
|
7
|
+
buttonLabel: string;
|
|
8
|
+
icon: React.ElementType;
|
|
9
|
+
action: "prev" | "next";
|
|
10
|
+
disabled: boolean | undefined;
|
|
11
|
+
};
|
|
12
|
+
export declare const NavigationButton: ({ publish, labels, buttonLabel, icon, action, disabled }: NavigationButtonProps) => JSX.Element;
|
|
13
|
+
export declare const Navigation: Component;
|
|
14
|
+
export declare const NavigationModule: import("../../types.js").Module;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createModule } from "../config.js";
|
|
3
|
+
import { cssClass, label } from "../utils.js";
|
|
4
|
+
import { IconButton, NextIcon, PreviousIcon } from "../components/index.js";
|
|
5
|
+
import { useEvents } from "../contexts/index.js";
|
|
6
|
+
import { useController } from "./Controller.js";
|
|
7
|
+
export const NavigationButton = ({ publish, labels, buttonLabel, icon, action, disabled }) => (React.createElement(IconButton, { label: label(labels, buttonLabel), icon: icon, className: cssClass(`navigation_${action}`), disabled: disabled, "aria-disabled": disabled, onClick: () => {
|
|
8
|
+
publish(action);
|
|
9
|
+
} }));
|
|
10
|
+
export const Navigation = ({ slides, carousel: { finite }, labels }) => {
|
|
11
|
+
const { currentIndex, subscribeSensors } = useController();
|
|
12
|
+
const { publish } = useEvents();
|
|
13
|
+
React.useEffect(() => subscribeSensors("onKeyUp", (event) => {
|
|
14
|
+
if (event.code === "ArrowLeft") {
|
|
15
|
+
publish("prev");
|
|
16
|
+
}
|
|
17
|
+
else if (event.code === "ArrowRight") {
|
|
18
|
+
publish("next");
|
|
19
|
+
}
|
|
20
|
+
}), [subscribeSensors, publish]);
|
|
21
|
+
return (React.createElement(React.Fragment, null,
|
|
22
|
+
React.createElement(NavigationButton, { buttonLabel: "Previous Image", action: "prev", icon: PreviousIcon, disabled: finite && currentIndex === 0, labels: labels, publish: publish }),
|
|
23
|
+
React.createElement(NavigationButton, { buttonLabel: "Next Image", action: "next", icon: NextIcon, disabled: finite && currentIndex === slides.length - 1, labels: labels, publish: publish })));
|
|
24
|
+
};
|
|
25
|
+
export const NavigationModule = createModule("navigation", Navigation);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createModule } from "../config.js";
|
|
3
|
+
import { cssClass, cssVar } from "../utils.js";
|
|
4
|
+
const noScroll = cssClass("no_scroll");
|
|
5
|
+
const padScrollbar = cssClass("pad_scrollbar");
|
|
6
|
+
const scrollbarPadding = cssVar("scrollbar_padding");
|
|
7
|
+
export const NoScroll = ({ children }) => {
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const scrollbarWidth = Math.round(window.innerWidth - document.documentElement.clientWidth);
|
|
10
|
+
if (scrollbarWidth > 1) {
|
|
11
|
+
document.body.style.setProperty(scrollbarPadding, `${scrollbarWidth}px`);
|
|
12
|
+
document.body.classList.add(padScrollbar);
|
|
13
|
+
}
|
|
14
|
+
document.body.classList.add(noScroll);
|
|
15
|
+
return () => {
|
|
16
|
+
document.body.style.removeProperty(scrollbarPadding);
|
|
17
|
+
document.body.classList.remove(noScroll, padScrollbar);
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
return React.createElement(React.Fragment, null, children);
|
|
21
|
+
};
|
|
22
|
+
export const NoScrollModule = createModule("no-scroll", NoScroll);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as ReactDOM from "react-dom";
|
|
3
|
+
import { LightboxDefaultProps } from "../../types.js";
|
|
4
|
+
import { createModule } from "../config.js";
|
|
5
|
+
import { cssClass, cssVar } from "../utils.js";
|
|
6
|
+
import { useEvents } from "../contexts/index.js";
|
|
7
|
+
export const Portal = ({ animation, children }) => {
|
|
8
|
+
const [mounted, setMounted] = React.useState(false);
|
|
9
|
+
const [portal] = React.useState(() => {
|
|
10
|
+
const div = document.createElement("div");
|
|
11
|
+
div.className = cssClass("portal");
|
|
12
|
+
if (animation.fade !== LightboxDefaultProps.animation.fade) {
|
|
13
|
+
div.style.setProperty(cssVar("fade_animation_duration"), `${Math.round(animation.fade)}ms`);
|
|
14
|
+
}
|
|
15
|
+
return div;
|
|
16
|
+
});
|
|
17
|
+
const { subscribe } = useEvents();
|
|
18
|
+
React.useEffect(() => subscribe("close", () => {
|
|
19
|
+
portal.classList.remove(cssClass("portal_open"));
|
|
20
|
+
}), [subscribe, portal]);
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
document.body.appendChild(portal);
|
|
23
|
+
setMounted(true);
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
portal.classList.add(cssClass("portal_open"));
|
|
26
|
+
}, 0);
|
|
27
|
+
return () => {
|
|
28
|
+
document.body.removeChild(portal);
|
|
29
|
+
setMounted(false);
|
|
30
|
+
};
|
|
31
|
+
}, [portal]);
|
|
32
|
+
return ReactDOM.createPortal(mounted ? children : null, portal);
|
|
33
|
+
};
|
|
34
|
+
export const PortalModule = createModule("portal", Portal);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createModule } from "../config.js";
|
|
3
|
+
import { cssClass, label } from "../utils.js";
|
|
4
|
+
import { useEvents } from "../contexts/index.js";
|
|
5
|
+
import { CloseIcon, IconButton } from "../components/index.js";
|
|
6
|
+
export const Toolbar = ({ toolbar: { buttons }, labels }) => {
|
|
7
|
+
const { publish } = useEvents();
|
|
8
|
+
return (React.createElement("div", { className: cssClass("toolbar") }, buttons?.map((button) => button === "close" ? (React.createElement(IconButton, { key: "close", label: label(labels, "Close"), icon: CloseIcon, onClick: () => publish("close") })) : (button))));
|
|
9
|
+
};
|
|
10
|
+
export const ToolbarModule = createModule("toolbar", Toolbar);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Labels } from "../types.js";
|
|
3
|
+
export declare const clsx: (...classes: (string | boolean | undefined)[]) => string;
|
|
4
|
+
export declare const cssClass: (name: string) => string;
|
|
5
|
+
export declare const cssVar: (name: string) => string;
|
|
6
|
+
export declare const label: (labels: Labels | undefined, lbl: string) => string;
|
|
7
|
+
export declare const cleanup: (...cleaners: (() => void)[]) => () => void;
|
|
8
|
+
export declare const makeUseContext: <T>(name: string, contextName: string, context: React.Context<T | null>) => () => T;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export const clsx = (...classes) => [...classes].filter((cls) => Boolean(cls)).join(" ");
|
|
3
|
+
export const cssClass = (name) => `yarl__${name}`;
|
|
4
|
+
export const cssVar = (name) => `--yarl__${name}`;
|
|
5
|
+
export const label = (labels, lbl) => (labels && labels[lbl] ? labels[lbl] : lbl);
|
|
6
|
+
export const cleanup = (...cleaners) => () => {
|
|
7
|
+
cleaners.forEach((cleaner) => {
|
|
8
|
+
cleaner();
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
export const makeUseContext = (name, contextName, context) => () => {
|
|
12
|
+
const ctx = React.useContext(context);
|
|
13
|
+
if (!ctx) {
|
|
14
|
+
throw new Error(`${name} must be used within a ${contextName}.Provider`);
|
|
15
|
+
}
|
|
16
|
+
return ctx;
|
|
17
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { LightboxProps, Plugin } from "../types.js";
|
|
3
|
+
declare module "../types.js" {
|
|
4
|
+
interface LightboxProps {
|
|
5
|
+
fullscreen?: boolean;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
declare global {
|
|
9
|
+
interface Document {
|
|
10
|
+
webkitFullscreenEnabled?: boolean;
|
|
11
|
+
mozFullScreenEnabled?: boolean;
|
|
12
|
+
msFullscreenEnabled?: boolean;
|
|
13
|
+
webkitExitFullscreen?: () => void;
|
|
14
|
+
mozCancelFullScreen?: () => void;
|
|
15
|
+
msExitFullscreen?: () => void;
|
|
16
|
+
webkitFullscreenElement?: Element;
|
|
17
|
+
mozFullScreenElement?: Element;
|
|
18
|
+
msFullscreenElement?: Element;
|
|
19
|
+
}
|
|
20
|
+
interface HTMLElement {
|
|
21
|
+
webkitRequestFullscreen?: () => void;
|
|
22
|
+
mozRequestFullScreen?: () => void;
|
|
23
|
+
msRequestFullscreen?: () => void;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export declare type FullscreenButtonProps = Pick<LightboxProps, "labels"> & {
|
|
27
|
+
auto: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare const FullscreenButton: ({ auto, labels }: FullscreenButtonProps) => JSX.Element | null;
|
|
30
|
+
export declare const Fullscreen: Plugin;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createIcon, IconButton, label, useController } from "../core/index.js";
|
|
3
|
+
const EnterFullscreenIcon = createIcon("EnterFullscreen", React.createElement("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }));
|
|
4
|
+
const ExitFullscreenIcon = createIcon("ExitFullscreen", React.createElement("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }));
|
|
5
|
+
export const FullscreenButton = ({ auto, labels }) => {
|
|
6
|
+
const [fullscreen, setFullscreen] = React.useState(false);
|
|
7
|
+
const { containerRef } = useController();
|
|
8
|
+
const isFullscreenEnabled = () => {
|
|
9
|
+
return (document.fullscreenEnabled ??
|
|
10
|
+
document.webkitFullscreenEnabled ??
|
|
11
|
+
document.mozFullScreenEnabled ??
|
|
12
|
+
document.msFullscreenEnabled ??
|
|
13
|
+
false);
|
|
14
|
+
};
|
|
15
|
+
const getFullscreenElement = React.useCallback(() => document.fullscreenElement ??
|
|
16
|
+
document.webkitFullscreenElement ??
|
|
17
|
+
document.mozFullScreenElement ??
|
|
18
|
+
document.msFullscreenElement, []);
|
|
19
|
+
const requestFullscreen = React.useCallback(() => {
|
|
20
|
+
const container = containerRef.current;
|
|
21
|
+
if (container) {
|
|
22
|
+
try {
|
|
23
|
+
if (container.requestFullscreen) {
|
|
24
|
+
container.requestFullscreen().catch(() => { });
|
|
25
|
+
}
|
|
26
|
+
else if (container.webkitRequestFullscreen) {
|
|
27
|
+
container.webkitRequestFullscreen();
|
|
28
|
+
}
|
|
29
|
+
else if (container.mozRequestFullScreen) {
|
|
30
|
+
container.mozRequestFullScreen();
|
|
31
|
+
}
|
|
32
|
+
else if (container.msRequestFullscreen) {
|
|
33
|
+
container.msRequestFullscreen();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [containerRef]);
|
|
40
|
+
const exitFullscreen = React.useCallback(() => {
|
|
41
|
+
if (getFullscreenElement()) {
|
|
42
|
+
try {
|
|
43
|
+
if (document.exitFullscreen) {
|
|
44
|
+
document.exitFullscreen().catch(() => { });
|
|
45
|
+
}
|
|
46
|
+
else if (document.webkitExitFullscreen) {
|
|
47
|
+
document.webkitExitFullscreen();
|
|
48
|
+
}
|
|
49
|
+
else if (document.mozCancelFullScreen) {
|
|
50
|
+
document.mozCancelFullScreen();
|
|
51
|
+
}
|
|
52
|
+
else if (document.msExitFullscreen) {
|
|
53
|
+
document.msExitFullscreen();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, [getFullscreenElement]);
|
|
60
|
+
const toggleFullscreen = React.useCallback(() => {
|
|
61
|
+
if (fullscreen) {
|
|
62
|
+
exitFullscreen();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
requestFullscreen();
|
|
66
|
+
}
|
|
67
|
+
}, [fullscreen, requestFullscreen, exitFullscreen]);
|
|
68
|
+
const fullscreenChangeListener = React.useCallback(() => {
|
|
69
|
+
if (getFullscreenElement() === containerRef.current) {
|
|
70
|
+
setFullscreen(true);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
setFullscreen(false);
|
|
74
|
+
}
|
|
75
|
+
}, [containerRef, getFullscreenElement]);
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
const events = ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"];
|
|
78
|
+
events.forEach((event) => {
|
|
79
|
+
document.addEventListener(event, fullscreenChangeListener);
|
|
80
|
+
});
|
|
81
|
+
return () => {
|
|
82
|
+
events.forEach((event) => {
|
|
83
|
+
document.removeEventListener(event, fullscreenChangeListener);
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
}, [fullscreenChangeListener]);
|
|
87
|
+
React.useEffect(() => () => exitFullscreen(), [exitFullscreen]);
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
if (auto) {
|
|
90
|
+
requestFullscreen();
|
|
91
|
+
}
|
|
92
|
+
}, [auto, requestFullscreen]);
|
|
93
|
+
if (!isFullscreenEnabled())
|
|
94
|
+
return null;
|
|
95
|
+
return (React.createElement(IconButton, { label: fullscreen ? label(labels, "Exit Fullscreen") : label(labels, "Enter Fullscreen"), icon: fullscreen ? ExitFullscreenIcon : EnterFullscreenIcon, onClick: toggleFullscreen }));
|
|
96
|
+
};
|
|
97
|
+
export const Fullscreen = ({ augment }) => {
|
|
98
|
+
augment(({ toolbar: { buttons, ...restToolbar }, ...restProps }) => ({
|
|
99
|
+
toolbar: {
|
|
100
|
+
buttons: [
|
|
101
|
+
React.createElement(FullscreenButton, { key: "fullscreen", auto: Boolean(restProps.fullscreen), labels: restProps.labels }),
|
|
102
|
+
...buttons,
|
|
103
|
+
],
|
|
104
|
+
...restToolbar,
|
|
105
|
+
},
|
|
106
|
+
...restProps,
|
|
107
|
+
}));
|
|
108
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Component, Plugin } from "../types.js";
|
|
3
|
+
declare module "../types.js" {
|
|
4
|
+
interface LightboxProps {
|
|
5
|
+
inline?: React.HTMLAttributes<HTMLDivElement>;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare const InlineContainer: Component;
|
|
9
|
+
export declare const InlineModule: import("../types.js").Module;
|
|
10
|
+
export declare const Inline: Plugin;
|