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