yet-another-react-lightbox 1.7.1 → 1.9.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/README.md +11 -7
- package/dist/core/components/IconButton.d.ts +1 -1
- package/dist/core/components/IconButton.js +2 -1
- package/dist/core/components/ImageSlide.d.ts +4 -3
- package/dist/core/components/ImageSlide.js +24 -31
- package/dist/core/config.js +16 -0
- package/dist/core/contexts/Timeouts.js +2 -2
- 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/hooks/useSensors.js +5 -2
- package/dist/core/modules/Carousel.js +1 -1
- package/dist/core/modules/Controller.d.ts +4 -3
- package/dist/core/modules/Controller.js +30 -25
- package/dist/core/modules/Navigation.js +8 -6
- package/dist/core/utils.d.ts +2 -0
- package/dist/core/utils.js +5 -0
- package/dist/plugins/Captions.js +2 -2
- package/dist/plugins/Fullscreen.d.ts +4 -4
- package/dist/plugins/Fullscreen.js +12 -3
- package/dist/plugins/Thumbnails.d.ts +48 -0
- package/dist/plugins/Thumbnails.js +241 -0
- package/dist/plugins/Zoom.d.ts +51 -0
- package/dist/plugins/Zoom.js +423 -0
- package/dist/plugins/captions.css +0 -1
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +2 -0
- package/dist/plugins/thumbnails.css +150 -0
- package/dist/styles.css +5 -7
- package/dist/types.d.ts +19 -5
- package/package.json +26 -17
package/README.md
CHANGED
|
@@ -14,10 +14,11 @@ Modern React lightbox component. Performant, easy to use, customizable and exten
|
|
|
14
14
|
- **Performance:** preloads limited number of images without compromising performance or UX
|
|
15
15
|
- **Responsive:** responsive images with automatic resolution switching are supported out of the box
|
|
16
16
|
- **Video:** video slides are supported via an optional plugin
|
|
17
|
+
- **Zoom:** image zoom is supported via an optional plugin
|
|
17
18
|
- **Customization:** customize any UI element or add your own custom slides
|
|
18
19
|
- **No bloat:** never bundle rarely used features; add optional features via plugins
|
|
19
|
-
- **RTL:** compatible with RTL layout
|
|
20
20
|
- **TypeScript:** type definitions come built-in in the package
|
|
21
|
+
- **RTL:** compatible with RTL layout
|
|
21
22
|
|
|
22
23
|
## Documentation
|
|
23
24
|
|
|
@@ -99,13 +100,14 @@ const App = () => {
|
|
|
99
100
|
{
|
|
100
101
|
src: "/image1x3840.jpg",
|
|
101
102
|
alt: "image 1",
|
|
102
|
-
|
|
103
|
+
width: 3840,
|
|
104
|
+
height: 2560,
|
|
103
105
|
srcSet: [
|
|
104
|
-
{ src: "/image1x320.jpg", width: 320 },
|
|
105
|
-
{ src: "/image1x640.jpg", width: 640 },
|
|
106
|
-
{ src: "/image1x1200.jpg", width: 1200 },
|
|
107
|
-
{ src: "/image1x2048.jpg", width: 2048 },
|
|
108
|
-
{ src: "/image1x3840.jpg", width: 3840 },
|
|
106
|
+
{ src: "/image1x320.jpg", width: 320, height: 213 },
|
|
107
|
+
{ src: "/image1x640.jpg", width: 640, height: 427 },
|
|
108
|
+
{ src: "/image1x1200.jpg", width: 1200, height: 800 },
|
|
109
|
+
{ src: "/image1x2048.jpg", width: 2048, height: 1365 },
|
|
110
|
+
{ src: "/image1x3840.jpg", width: 3840, height: 2560 },
|
|
109
111
|
]
|
|
110
112
|
},
|
|
111
113
|
// ...
|
|
@@ -132,7 +134,9 @@ The following plugins come bundled in the package:
|
|
|
132
134
|
- [Fullscreen](https://yet-another-react-lightbox.vercel.app/plugins/fullscreen) - adds support for fullscreen mode
|
|
133
135
|
- [Inline](https://yet-another-react-lightbox.vercel.app/plugins/inline) - adds support for inline rendering mode
|
|
134
136
|
- [Slideshow](https://yet-another-react-lightbox.vercel.app/plugins/slideshow) - adds slideshow autoplay feature
|
|
137
|
+
- [Thumbnails](https://yet-another-react-lightbox.vercel.app/plugins/thumbnails) - adds thumbnails track
|
|
135
138
|
- [Video](https://yet-another-react-lightbox.vercel.app/plugins/video) - adds support for video slides
|
|
139
|
+
- [Zoom](https://yet-another-react-lightbox.vercel.app/plugins/zoom) - adds zoom feature
|
|
136
140
|
|
|
137
141
|
## License
|
|
138
142
|
|
|
@@ -4,4 +4,4 @@ export declare type IconButtonProps = Omit<React.DetailedHTMLProps<React.ButtonH
|
|
|
4
4
|
icon: React.ElementType;
|
|
5
5
|
renderIcon?: () => React.ReactNode;
|
|
6
6
|
};
|
|
7
|
-
export declare const IconButton: React.
|
|
7
|
+
export declare const IconButton: React.ForwardRefExoticComponent<Pick<IconButtonProps, "value" | "children" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerLeave" | "onPointerCancel" | "onTouchStart" | "onTouchMove" | "onTouchEnd" | "onTouchCancel" | "onKeyDown" | "onKeyUp" | "onWheel" | "key" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "id" | "lang" | "placeholder" | "slot" | "spellCheck" | "style" | "tabIndex" | "title" | "translate" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancelCapture" | "onTouchEndCapture" | "onTouchMoveCapture" | "onTouchStartCapture" | "onPointerDownCapture" | "onPointerMoveCapture" | "onPointerUpCapture" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "form" | "label" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "name" | "icon" | "renderIcon"> & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { clsx, cssClass } from "../utils.js";
|
|
3
|
-
export const IconButton = ({ label, className, icon: Icon, renderIcon, onClick, ...rest }) => (React.createElement("button", { type: "button", "aria-label": label, className: clsx(cssClass("button"), className), onClick: onClick, ...rest }, renderIcon ? renderIcon() : React.createElement(Icon, { className: cssClass("icon") })));
|
|
3
|
+
export const IconButton = React.forwardRef(({ label, className, icon: Icon, renderIcon, onClick, ...rest }, ref) => (React.createElement("button", { ref: ref, type: "button", "aria-label": label, className: clsx(cssClass("button"), className), onClick: onClick, ...rest }, renderIcon ? renderIcon() : React.createElement(Icon, { className: cssClass("icon") }))));
|
|
4
|
+
IconButton.displayName = "IconButton";
|
|
@@ -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
|
-
|
|
9
|
-
var _a;
|
|
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 }) => {
|
|
8
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
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(() => {
|
|
@@ -42,33 +40,28 @@ export const ImageSlide = ({ slide: image, offset, render, rect }) => {
|
|
|
42
40
|
const onError = React.useCallback(() => {
|
|
43
41
|
setStatus(SLIDE_STATUS_ERROR);
|
|
44
42
|
}, []);
|
|
43
|
+
const cover = image.imageFit === "cover" || (image.imageFit !== "contain" && imageFit === "cover");
|
|
44
|
+
const nonInfinite = (value, fallback) => (Number.isFinite(value) ? value : fallback);
|
|
45
|
+
const maxWidth = adjustDevicePixelRatio(nonInfinite(Math.max(...((_b = (_a = image.srcSet) === null || _a === void 0 ? void 0 : _a.map((x) => x.width)) !== null && _b !== void 0 ? _b : []).concat(image.width ? [image.width] : [])), ((_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalWidth) || 0));
|
|
46
|
+
const maxHeight = adjustDevicePixelRatio(nonInfinite(Math.max(...((_e = (_d = image.srcSet) === null || _d === void 0 ? void 0 : _d.map((x) => x.height).filter((x) => Boolean(x))) !== null && _e !== void 0 ? _e : []).concat(image.height ? [image.height] : [])), (image.aspectRatio && maxWidth ? maxWidth / image.aspectRatio : (_f = imageRef.current) === null || _f === void 0 ? void 0 : _f.naturalHeight) || 0));
|
|
47
|
+
const style = maxWidth && maxHeight ? { maxWidth, maxHeight } : undefined;
|
|
48
|
+
const srcSet = (_g = image.srcSet) === null || _g === void 0 ? void 0 : _g.sort((a, b) => a.width - b.width).map((item) => `${item.src} ${item.width}w`).join(", ");
|
|
49
|
+
const estimateActualWidth = () => {
|
|
50
|
+
if (rect && !cover) {
|
|
51
|
+
if (image.width && image.height) {
|
|
52
|
+
return (rect.height / image.height) * image.width;
|
|
53
|
+
}
|
|
54
|
+
if (image.aspectRatio) {
|
|
55
|
+
return rect.height * image.aspectRatio;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Number.MAX_VALUE;
|
|
59
|
+
};
|
|
60
|
+
const sizes = srcSet && rect && hasWindow()
|
|
61
|
+
? `${Math.ceil((Math.min(estimateActualWidth(), rect.width) / window.innerWidth) * 100)}vw`
|
|
62
|
+
: undefined;
|
|
45
63
|
return (React.createElement(React.Fragment, null,
|
|
46
|
-
React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, className: clsx(cssClass("slide_image"), (
|
|
47
|
-
(image.imageFit !== "contain" && latestProps.current.carousel.imageFit === "cover")) &&
|
|
48
|
-
cssClass("slide_image_cover"), status !== SLIDE_STATUS_COMPLETE && cssClass("slide_image_loading")), draggable: false, alt: image.alt, ...(image.srcSet
|
|
49
|
-
? {
|
|
50
|
-
...(rect && hasWindow()
|
|
51
|
-
? {
|
|
52
|
-
sizes: `${Math.ceil((Math.min(image.aspectRatio ? rect.height * image.aspectRatio : Number.MAX_VALUE, rect.width) /
|
|
53
|
-
window.innerWidth) *
|
|
54
|
-
100)}vw`,
|
|
55
|
-
}
|
|
56
|
-
: null),
|
|
57
|
-
srcSet: image.srcSet
|
|
58
|
-
.sort((a, b) => a.width - b.width)
|
|
59
|
-
.map((item) => `${item.src} ${item.width}w`)
|
|
60
|
-
.join(", "),
|
|
61
|
-
style: {
|
|
62
|
-
maxWidth: `${adjustDevicePixelRatio(Math.max(...image.srcSet.map((x) => x.width)))}px`,
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
: {
|
|
66
|
-
style: imageRef.current && ((_a = imageRef.current) === null || _a === void 0 ? void 0 : _a.naturalWidth) > 0
|
|
67
|
-
? {
|
|
68
|
-
maxWidth: `${adjustDevicePixelRatio(imageRef.current.naturalWidth)}px`,
|
|
69
|
-
}
|
|
70
|
-
: undefined,
|
|
71
|
-
}), src: image.src }),
|
|
64
|
+
React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, className: clsx(cssClass("slide_image"), cssClass("fullsize"), cover && cssClass("slide_image_cover"), status !== SLIDE_STATUS_COMPLETE && cssClass("slide_image_loading")), draggable: false, alt: image.alt, style: style, sizes: sizes, srcSet: srcSet, src: image.src }),
|
|
72
65
|
status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass("slide_placeholder") },
|
|
73
66
|
status === SLIDE_STATUS_LOADING &&
|
|
74
67
|
((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass("icon"), cssClass("slide_loading")) }))),
|
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,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { makeUseContext } from "../utils.js";
|
|
2
|
+
import { isDefined, makeUseContext } from "../utils.js";
|
|
3
3
|
const TimeoutsContext = React.createContext(null);
|
|
4
4
|
export const useTimeouts = makeUseContext("useTimeouts", "TimeoutsContext", TimeoutsContext);
|
|
5
5
|
export const TimeoutsProvider = ({ children }) => {
|
|
@@ -16,7 +16,7 @@ export const TimeoutsProvider = ({ children }) => {
|
|
|
16
16
|
return id;
|
|
17
17
|
};
|
|
18
18
|
const clearTimeout = (id) => {
|
|
19
|
-
if (
|
|
19
|
+
if (isDefined(id)) {
|
|
20
20
|
removeTimeout(id);
|
|
21
21
|
window.clearTimeout(id);
|
|
22
22
|
}
|
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
|
+
};
|
|
@@ -4,7 +4,10 @@ export const useSensors = () => {
|
|
|
4
4
|
return React.useMemo(() => {
|
|
5
5
|
const notifySubscribers = (type, event) => {
|
|
6
6
|
var _a;
|
|
7
|
-
(_a = subscribers[type]) === null || _a === void 0 ? void 0 : _a.forEach((listener) =>
|
|
7
|
+
(_a = subscribers[type]) === null || _a === void 0 ? void 0 : _a.forEach((listener) => {
|
|
8
|
+
if (!event.isPropagationStopped())
|
|
9
|
+
listener(event);
|
|
10
|
+
});
|
|
8
11
|
};
|
|
9
12
|
return {
|
|
10
13
|
registerSensors: {
|
|
@@ -25,7 +28,7 @@ export const useSensors = () => {
|
|
|
25
28
|
if (!subscribers[type]) {
|
|
26
29
|
subscribers[type] = [];
|
|
27
30
|
}
|
|
28
|
-
subscribers[type].
|
|
31
|
+
subscribers[type].unshift(callback);
|
|
29
32
|
return () => {
|
|
30
33
|
const listeners = subscribers[type];
|
|
31
34
|
if (listeners) {
|
|
@@ -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),
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Component, ComponentProps } from "../../types.js";
|
|
3
|
-
import { SubscribeSensors } from "../hooks/index.js";
|
|
3
|
+
import { ContainerRect, 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>;
|
|
11
|
+
transferFocus: () => void;
|
|
12
|
+
containerRect: ContainerRect;
|
|
13
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
13
14
|
};
|
|
14
15
|
export declare const useController: () => ControllerContextType;
|
|
15
16
|
export declare const Controller: Component;
|
|
@@ -2,20 +2,20 @@ 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);
|
|
9
9
|
export const useController = makeUseContext("useController", "ControllerContext", ControllerContext);
|
|
10
10
|
export const Controller = ({ children, ...props }) => {
|
|
11
|
-
const { containerRef, setContainerRef } = useContainerRect();
|
|
11
|
+
const { containerRef, setContainerRef, containerRect } = useContainerRect();
|
|
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({
|
|
@@ -31,7 +31,7 @@ export const Controller = ({ children, ...props }) => {
|
|
|
31
31
|
refs.current.props = props;
|
|
32
32
|
useEnhancedEffect(() => {
|
|
33
33
|
const preventDefault = (event) => {
|
|
34
|
-
if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
|
|
34
|
+
if (Math.abs(event.deltaX) > Math.abs(event.deltaY) || event.ctrlKey) {
|
|
35
35
|
event.preventDefault();
|
|
36
36
|
}
|
|
37
37
|
};
|
|
@@ -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");
|
|
@@ -273,15 +269,25 @@ export const Controller = ({ children, ...props }) => {
|
|
|
273
269
|
}
|
|
274
270
|
}, [updateSwipeOffset, setTimeout, clearTimeout, swipe, resetSwipe, rerender, isSwipeValid, containerRef]);
|
|
275
271
|
React.useEffect(() => subscribeSensors("onWheel", onWheel), [subscribeSensors, onWheel]);
|
|
272
|
+
const transferFocus = React.useCallback(() => { var _a; return (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, [containerRef]);
|
|
276
273
|
const context = React.useMemo(() => ({
|
|
277
274
|
latestProps,
|
|
278
|
-
containerRef,
|
|
279
275
|
currentIndex: state.currentIndex,
|
|
280
276
|
globalIndex: state.globalIndex,
|
|
281
|
-
isRTL: state.isRTL,
|
|
282
277
|
subscribeSensors,
|
|
283
|
-
|
|
284
|
-
|
|
278
|
+
transferFocus,
|
|
279
|
+
containerRect,
|
|
280
|
+
containerRef,
|
|
281
|
+
}), [
|
|
282
|
+
latestProps,
|
|
283
|
+
state.currentIndex,
|
|
284
|
+
state.globalIndex,
|
|
285
|
+
subscribeSensors,
|
|
286
|
+
transferFocus,
|
|
287
|
+
containerRect,
|
|
288
|
+
containerRef,
|
|
289
|
+
]);
|
|
290
|
+
return (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass("container"), cssClass("fullsize"), refs.current.swipeState === "swipe" && cssClass("container_swipe")), style: {
|
|
285
291
|
...(refs.current.swipeAnimationDuration !== LightboxDefaultProps.animation.swipe
|
|
286
292
|
? {
|
|
287
293
|
[cssVar("swipe_animation_duration")]: `${Math.round(refs.current.swipeAnimationDuration)}ms`,
|
|
@@ -292,7 +298,6 @@ export const Controller = ({ children, ...props }) => {
|
|
|
292
298
|
[cssVar("controller_touch_action")]: props.controller.touchAction,
|
|
293
299
|
}
|
|
294
300
|
: null),
|
|
295
|
-
}, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors },
|
|
296
|
-
React.createElement(ControllerContext.Provider, { value: context }, children)));
|
|
301
|
+
}, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context }, children))));
|
|
297
302
|
};
|
|
298
303
|
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();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const isRTL = useLatest(useRTL());
|
|
15
|
+
React.useEffect(() => subscribeSensors("onKeyDown", (event) => {
|
|
16
|
+
if (event.key === "ArrowLeft") {
|
|
17
|
+
publish(isRTL.current ? "next" : "prev");
|
|
16
18
|
}
|
|
17
|
-
else if (event.
|
|
18
|
-
publish(isRTL ? "prev" : "next");
|
|
19
|
+
else if (event.key === "ArrowRight") {
|
|
20
|
+
publish(isRTL.current ? "prev" : "next");
|
|
19
21
|
}
|
|
20
22
|
}), [subscribeSensors, publish, isRTL]);
|
|
21
23
|
return (React.createElement(React.Fragment, null,
|
package/dist/core/utils.d.ts
CHANGED
|
@@ -7,4 +7,6 @@ export declare const label: (labels: Labels | undefined, lbl: string) => string;
|
|
|
7
7
|
export declare const cleanup: (...cleaners: (() => void)[]) => () => void;
|
|
8
8
|
export declare const makeUseContext: <T>(name: string, contextName: string, context: React.Context<T | null>) => () => T;
|
|
9
9
|
export declare const hasWindow: () => boolean;
|
|
10
|
+
export declare const isDefined: <T = any>(x: T | undefined) => x is T;
|
|
10
11
|
export declare const adjustDevicePixelRatio: (value: number) => number;
|
|
12
|
+
export declare const round: (value: number, decimals?: number) => number;
|
package/dist/core/utils.js
CHANGED
|
@@ -17,4 +17,9 @@ export const makeUseContext = (name, contextName, context) => () => {
|
|
|
17
17
|
return ctx;
|
|
18
18
|
};
|
|
19
19
|
export const hasWindow = () => typeof window !== "undefined";
|
|
20
|
+
export const isDefined = (x) => typeof x !== "undefined";
|
|
20
21
|
export const adjustDevicePixelRatio = (value) => hasWindow() ? Math.round(value / (window.devicePixelRatio || 1)) : value;
|
|
22
|
+
export const round = (value, decimals = 0) => {
|
|
23
|
+
const factor = 10 ** decimals;
|
|
24
|
+
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
25
|
+
};
|
package/dist/plugins/Captions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { cssClass, cssVar, makeUseContext } from "../core/utils.js";
|
|
2
|
+
import { cssClass, cssVar, isDefined, makeUseContext } from "../core/utils.js";
|
|
3
3
|
import { useEvents } from "../core/contexts/Events.js";
|
|
4
4
|
import { createModule } from "../core/index.js";
|
|
5
5
|
const defaultTextAlign = "start";
|
|
@@ -27,7 +27,7 @@ export const CaptionsComponent = ({ children }) => {
|
|
|
27
27
|
const { subscribe } = useEvents();
|
|
28
28
|
const [toolbarWidth, setToolbarWidth] = React.useState();
|
|
29
29
|
React.useEffect(() => subscribe("toolbar-width", (topic, event) => {
|
|
30
|
-
if (
|
|
30
|
+
if (!isDefined(event) || typeof event === "number") {
|
|
31
31
|
setToolbarWidth(event);
|
|
32
32
|
}
|
|
33
33
|
}), [subscribe]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { LightboxProps, Plugin
|
|
2
|
+
import { Component, LightboxProps, Plugin } 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,13 @@ declare global {
|
|
|
35
35
|
msRequestFullscreen?: () => void;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
export declare const FullscreenContainer: Component;
|
|
38
39
|
/** Fullscreen button props */
|
|
39
|
-
export declare type FullscreenButtonProps = Pick<LightboxProps, "labels"> & {
|
|
40
|
+
export declare type FullscreenButtonProps = Pick<LightboxProps, "labels" | "render"> & {
|
|
40
41
|
auto: boolean;
|
|
41
|
-
render: Render;
|
|
42
42
|
};
|
|
43
43
|
/** Fullscreen button */
|
|
44
|
-
export declare const FullscreenButton:
|
|
44
|
+
export declare const FullscreenButton: React.FC<FullscreenButtonProps>;
|
|
45
45
|
/** Fullscreen plugin */
|
|
46
46
|
export declare const Fullscreen: Plugin;
|
|
47
47
|
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;
|