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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/Lightbox.d.ts +49 -0
  4. package/dist/Lightbox.js +29 -0
  5. package/dist/core/components/IconButton.d.ts +6 -0
  6. package/dist/core/components/IconButton.js +4 -0
  7. package/dist/core/components/Icons.d.ts +25 -0
  8. package/dist/core/components/Icons.js +14 -0
  9. package/dist/core/components/ImageSlide.d.ts +5 -0
  10. package/dist/core/components/ImageSlide.js +60 -0
  11. package/dist/core/components/index.d.ts +3 -0
  12. package/dist/core/components/index.js +3 -0
  13. package/dist/core/config.d.ts +7 -0
  14. package/dist/core/config.js +70 -0
  15. package/dist/core/contexts/Events.d.ts +16 -0
  16. package/dist/core/contexts/Events.js +31 -0
  17. package/dist/core/contexts/Timeouts.d.ts +8 -0
  18. package/dist/core/contexts/Timeouts.js +35 -0
  19. package/dist/core/contexts/index.d.ts +2 -0
  20. package/dist/core/contexts/index.js +2 -0
  21. package/dist/core/hooks/index.d.ts +4 -0
  22. package/dist/core/hooks/index.js +4 -0
  23. package/dist/core/hooks/useContainerRect.d.ts +10 -0
  24. package/dist/core/hooks/useContainerRect.js +33 -0
  25. package/dist/core/hooks/useEnhancedEffect.d.ts +2 -0
  26. package/dist/core/hooks/useEnhancedEffect.js +2 -0
  27. package/dist/core/hooks/useLatest.d.ts +2 -0
  28. package/dist/core/hooks/useLatest.js +6 -0
  29. package/dist/core/hooks/useSensors.d.ts +15 -0
  30. package/dist/core/hooks/useSensors.js +37 -0
  31. package/dist/core/index.d.ts +6 -0
  32. package/dist/core/index.js +6 -0
  33. package/dist/core/modules/Carousel.d.ts +3 -0
  34. package/dist/core/modules/Carousel.js +34 -0
  35. package/dist/core/modules/Controller.d.ts +13 -0
  36. package/dist/core/modules/Controller.js +267 -0
  37. package/dist/core/modules/Core.d.ts +3 -0
  38. package/dist/core/modules/Core.js +6 -0
  39. package/dist/core/modules/Navigation.d.ts +14 -0
  40. package/dist/core/modules/Navigation.js +25 -0
  41. package/dist/core/modules/NoScroll.d.ts +3 -0
  42. package/dist/core/modules/NoScroll.js +22 -0
  43. package/dist/core/modules/Portal.d.ts +3 -0
  44. package/dist/core/modules/Portal.js +34 -0
  45. package/dist/core/modules/Toolbar.d.ts +3 -0
  46. package/dist/core/modules/Toolbar.js +10 -0
  47. package/dist/core/modules/index.d.ts +7 -0
  48. package/dist/core/modules/index.js +7 -0
  49. package/dist/core/utils.d.ts +8 -0
  50. package/dist/core/utils.js +17 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +2 -0
  53. package/dist/plugins/Fullscreen.d.ts +30 -0
  54. package/dist/plugins/Fullscreen.js +108 -0
  55. package/dist/plugins/Inline.d.ts +10 -0
  56. package/dist/plugins/Inline.js +17 -0
  57. package/dist/plugins/Video.d.ts +21 -0
  58. package/dist/plugins/Video.js +44 -0
  59. package/dist/plugins/index.d.ts +3 -0
  60. package/dist/plugins/index.js +3 -0
  61. package/dist/styles.css +237 -0
  62. package/dist/types.d.ts +102 -0
  63. package/dist/types.js +61 -0
  64. 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,3 @@
1
+ import { Component } from "../../types.js";
2
+ export declare const Core: Component;
3
+ export declare const CoreModule: import("../../types.js").Module;
@@ -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,3 @@
1
+ import { Component } from "../../types.js";
2
+ export declare const NoScroll: Component;
3
+ export declare const NoScrollModule: import("../../types.js").Module;
@@ -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,3 @@
1
+ import { Component } from "../../types.js";
2
+ export declare const Portal: Component;
3
+ export declare const PortalModule: import("../../types.js").Module;
@@ -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,3 @@
1
+ import { Component } from "../../types.js";
2
+ export declare const Toolbar: Component;
3
+ export declare const ToolbarModule: import("../../types.js").Module;
@@ -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,7 @@
1
+ export * from "./Carousel.js";
2
+ export * from "./Controller.js";
3
+ export * from "./Core.js";
4
+ export * from "./Navigation.js";
5
+ export * from "./NoScroll.js";
6
+ export * from "./Portal.js";
7
+ export * from "./Toolbar.js";
@@ -0,0 +1,7 @@
1
+ export * from "./Carousel.js";
2
+ export * from "./Controller.js";
3
+ export * from "./Core.js";
4
+ export * from "./Navigation.js";
5
+ export * from "./NoScroll.js";
6
+ export * from "./Portal.js";
7
+ export * from "./Toolbar.js";
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./Lightbox.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./Lightbox.js";
@@ -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;