yet-another-react-lightbox 1.3.4 → 1.4.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
@@ -72,4 +72,4 @@ export default App;
72
72
 
73
73
  ## License
74
74
 
75
- MIT © [Igor Danchenko](https://github.com/igordanchenko)
75
+ MIT © 2022 [Igor Danchenko](https://github.com/igordanchenko)
package/dist/Lightbox.js CHANGED
@@ -24,7 +24,6 @@ const LightboxComponent = (props) => {
24
24
  return React.createElement(React.Fragment, null, renderNode(createNode(CoreModule, config), augmentedProps));
25
25
  };
26
26
  LightboxComponent.propTypes = LightboxPropTypes;
27
- /** Modern React lightbox component */
28
27
  export const Lightbox = (props) => {
29
28
  const { carousel, animation, render, toolbar, controller, on, ...restProps } = props;
30
29
  const { carousel: defaultCarousel, animation: defaultAnimation, render: defaultRender, toolbar: defaultToolbar, controller: defaultController, on: defaultOn, ...restDefaultProps } = LightboxDefaultProps;
@@ -10,7 +10,5 @@ export const createIcon = (name, glyph) => {
10
10
  export const CloseIcon = createIcon("Close", React.createElement("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }));
11
11
  export const PreviousIcon = createIcon("Previous", React.createElement("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }));
12
12
  export const NextIcon = createIcon("Next", React.createElement("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }));
13
- export const LoadingIcon = createIcon("Loading", React.createElement(React.Fragment, null, Array.from({ length: 8 }).map((_, index, array) => (React.createElement("line", {
14
- // eslint-disable-next-line react/no-array-index-key
15
- key: index, x1: "12", y1: "6.5", x2: "12", y2: "1.8", strokeLinecap: "round", strokeWidth: "2.6", stroke: "currentColor", strokeOpacity: (1 / array.length) * (index + 1), transform: `rotate(${(360 / array.length) * index}, 12, 12)` })))));
13
+ export const LoadingIcon = createIcon("Loading", React.createElement(React.Fragment, null, Array.from({ length: 8 }).map((_, index, array) => (React.createElement("line", { key: index, x1: "12", y1: "6.5", x2: "12", y2: "1.8", strokeLinecap: "round", strokeWidth: "2.6", stroke: "currentColor", strokeOpacity: (1 / array.length) * (index + 1), transform: `rotate(${(360 / array.length) * index}, 12, 12)` })))));
16
14
  export const ErrorIcon = createIcon("Error", React.createElement("path", { d: "M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z" }));
@@ -1,7 +1,9 @@
1
1
  /// <reference types="react" />
2
2
  import { Render, SlideImage } from "../../types.js";
3
+ import { ContainerRect } from "../hooks/index.js";
3
4
  export declare type ImageSlideProps = {
4
5
  slide: SlideImage;
5
- render: Render;
6
+ render?: Render;
7
+ rect?: ContainerRect;
6
8
  };
7
- export declare const ImageSlide: ({ slide: image, render }: ImageSlideProps) => JSX.Element;
9
+ export declare const ImageSlide: ({ slide: image, render, rect }: ImageSlideProps) => JSX.Element;
@@ -2,13 +2,11 @@ import * as React from "react";
2
2
  import { clsx, cssClass } from "../utils.js";
3
3
  import { useLatest } from "../hooks/index.js";
4
4
  import { ErrorIcon, LoadingIcon } from "./Icons.js";
5
- import { useController } from "../modules/Controller.js";
6
- export const ImageSlide = ({ slide: image, render }) => {
5
+ export const ImageSlide = ({ slide: image, render, rect }) => {
7
6
  var _a, _b, _c;
8
7
  const [state, setState] = React.useState("loading");
9
8
  const latestState = useLatest(state);
10
9
  const imageRef = React.useRef(null);
11
- const { containerRect } = useController();
12
10
  const handleLoading = React.useCallback((img) => {
13
11
  if (latestState.current === "complete") {
14
12
  return;
@@ -37,11 +35,13 @@ export const ImageSlide = ({ slide: image, render }) => {
37
35
  return (React.createElement(React.Fragment, null,
38
36
  React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, className: clsx(cssClass("slide_image"), state !== "complete" && cssClass("slide_image_loading")), draggable: false, alt: image.alt, ...(image.srcSet
39
37
  ? {
40
- // this approach does not account for carousel padding,
41
- // but the margin of error should be negligible in most cases
42
- sizes: `${Math.ceil((Math.min(image.aspectRatio ? containerRect.height * image.aspectRatio : Number.MAX_VALUE, containerRect.width) /
43
- window.innerWidth) *
44
- 100)}vw`,
38
+ ...(rect && typeof window !== "undefined"
39
+ ? {
40
+ sizes: `${Math.ceil((Math.min(image.aspectRatio ? rect.height * image.aspectRatio : Number.MAX_VALUE, rect.width) /
41
+ window.innerWidth) *
42
+ 100)}vw`,
43
+ }
44
+ : null),
45
45
  srcSet: image.srcSet
46
46
  .sort((a, b) => a.width - b.width)
47
47
  .map((item) => `${item.src} ${item.width}w`)
@@ -59,7 +59,7 @@ export const ImageSlide = ({ slide: image, render }) => {
59
59
  }), src: image.src }),
60
60
  state !== "complete" && (React.createElement("div", { className: cssClass("slide_placeholder") },
61
61
  state === "loading" &&
62
- (render.iconLoading ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass("icon"), cssClass("slide_loading")) }))),
62
+ ((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass("icon"), cssClass("slide_loading")) }))),
63
63
  state === "error" &&
64
- (render.iconError ? (render.iconError()) : (React.createElement(ErrorIcon, { className: clsx(cssClass("icon"), cssClass("slide_error")) })))))));
64
+ ((render === null || render === void 0 ? void 0 : render.iconError) ? (render.iconError()) : (React.createElement(ErrorIcon, { className: clsx(cssClass("icon"), cssClass("slide_error")) })))))));
65
65
  };
@@ -1,35 +1,40 @@
1
1
  import * as React from "react";
2
2
  import { LightboxDefaultProps } from "../../types.js";
3
+ import { useContainerRect } from "../hooks/index.js";
3
4
  import { createModule } from "../config.js";
4
5
  import { clsx, cssClass, cssVar } from "../utils.js";
5
6
  import { ImageSlide } from "../components/index.js";
6
7
  import { useController } from "./Controller.js";
7
- const CarouselSlide = ({ slide, offset, render }) => {
8
- const renderSlide = () => {
9
- if (render.slide) {
10
- const rendered = render.slide(slide);
11
- if (rendered) {
12
- return rendered;
13
- }
8
+ const CarouselSlide = ({ slide, offset }) => {
9
+ const { setContainerRef, containerRect } = useContainerRect();
10
+ const { latestProps } = useController();
11
+ const { render } = latestProps.current;
12
+ const renderSlide = (rect) => {
13
+ var _a, _b, _c, _d;
14
+ let rendered = (_a = render.slide) === null || _a === void 0 ? void 0 : _a.call(render, slide, offset, rect);
15
+ if (!rendered && "src" in slide) {
16
+ rendered = React.createElement(ImageSlide, { slide: slide, render: render, rect: rect });
14
17
  }
15
- return "src" in slide ? React.createElement(ImageSlide, { slide: slide, render: render }) : null;
18
+ return rendered ? (React.createElement(React.Fragment, null, (_b = render.slideHeader) === null || _b === void 0 ? void 0 :
19
+ _b.call(render, slide),
20
+ ((_c = render.slideContainer) !== null && _c !== void 0 ? _c : ((_, x) => x))(slide, rendered), (_d = render.slideFooter) === null || _d === void 0 ? void 0 :
21
+ _d.call(render, slide))) : null;
16
22
  };
17
- return (React.createElement("div", { className: clsx(cssClass("slide"), cssClass("flex_center")), style: { [cssVar("slide_offset")]: offset } }, renderSlide()));
23
+ return (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass("slide"), cssClass("flex_center")), style: { [cssVar("slide_offset")]: offset } }, containerRect && renderSlide(containerRect)));
18
24
  };
19
- export const Carousel = (props) => {
20
- const { slides, carousel: { finite, preload, padding, spacing }, render, } = props;
25
+ export const Carousel = ({ slides, carousel: { finite, preload, padding, spacing } }) => {
21
26
  const { currentIndex, globalIndex } = useController();
22
27
  const items = [];
23
28
  if ((slides === null || slides === void 0 ? void 0 : slides.length) > 0) {
24
29
  for (let i = currentIndex - preload; i < currentIndex; i += 1) {
25
30
  if (!finite || i >= 0) {
26
- items.push(React.createElement(CarouselSlide, { key: globalIndex + i - currentIndex, slide: slides[(i + preload * slides.length) % slides.length], offset: i - currentIndex, render: render }));
31
+ items.push(React.createElement(CarouselSlide, { key: globalIndex + i - currentIndex, slide: slides[(i + preload * slides.length) % slides.length], offset: i - currentIndex }));
27
32
  }
28
33
  }
29
- items.push(React.createElement(CarouselSlide, { key: globalIndex, slide: slides[currentIndex], offset: 0, render: render }));
34
+ items.push(React.createElement(CarouselSlide, { key: globalIndex, slide: slides[currentIndex], offset: 0 }));
30
35
  for (let i = currentIndex + 1; i <= currentIndex + preload; i += 1) {
31
36
  if (!finite || i <= slides.length - 1) {
32
- items.push(React.createElement(CarouselSlide, { key: globalIndex + i - currentIndex, slide: slides[i % slides.length], offset: i - currentIndex, render: render }));
37
+ items.push(React.createElement(CarouselSlide, { key: globalIndex + i - currentIndex, slide: slides[i % slides.length], offset: i - currentIndex }));
33
38
  }
34
39
  }
35
40
  }
@@ -1,9 +1,9 @@
1
1
  import * as React from "react";
2
- import { Component } from "../../types.js";
3
- import { ContainerRect, SubscribeSensors } from "../hooks/index.js";
2
+ import { Component, ComponentProps } from "../../types.js";
3
+ import { SubscribeSensors } from "../hooks/index.js";
4
4
  export declare type ControllerContextType = {
5
+ latestProps: React.MutableRefObject<ComponentProps>;
5
6
  containerRef: React.RefObject<HTMLDivElement>;
6
- containerRect: ContainerRect;
7
7
  currentIndex: number;
8
8
  globalIndex: number;
9
9
  subscribeSensors: SubscribeSensors<HTMLDivElement>;
@@ -2,13 +2,13 @@ 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, useSensors } from "../hooks/index.js";
5
+ import { useContainerRect, useEnhancedEffect, useLatest, 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, containerRect } = useContainerRect();
11
+ const { containerRef, setContainerRef } = useContainerRect();
12
12
  const { registerSensors, subscribeSensors } = useSensors();
13
13
  const { subscribe, publish } = useEvents();
14
14
  const { setTimeout, clearTimeout } = useTimeouts();
@@ -16,6 +16,7 @@ export const Controller = ({ children, ...props }) => {
16
16
  currentIndex: props.index,
17
17
  globalIndex: props.index,
18
18
  });
19
+ const latestProps = useLatest(props);
19
20
  const refs = React.useRef({
20
21
  state,
21
22
  props,
@@ -27,9 +28,6 @@ export const Controller = ({ children, ...props }) => {
27
28
  });
28
29
  refs.current.state = state;
29
30
  refs.current.props = props;
30
- refs.current.containerRect = containerRect;
31
- // prevent browser back/forward navigation on touchpad left/right swipe
32
- // this has to be done via non-passive native event handler
33
31
  useEnhancedEffect(() => {
34
32
  const preventDefault = (event) => event.preventDefault();
35
33
  const node = containerRef.current;
@@ -93,7 +91,7 @@ export const Controller = ({ children, ...props }) => {
93
91
  let newSwipeState = "swipe-animation";
94
92
  let newSwipeAnimationDuration = swipeAnimationDuration;
95
93
  if (!direction) {
96
- const containerWidth = (_a = current.containerRect) === null || _a === void 0 ? void 0 : _a.width;
94
+ const containerWidth = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth;
97
95
  const elapsedTime = current.swipeStartTime ? Date.now() - current.swipeStartTime : 0;
98
96
  const expectedTime = containerWidth
99
97
  ? (swipeAnimationDuration / containerWidth) * Math.abs(swipeOffset)
@@ -107,7 +105,6 @@ export const Controller = ({ children, ...props }) => {
107
105
  newSwipeAnimationDuration =
108
106
  (newSwipeAnimationDuration / expectedTime) * Math.max(elapsedTime, expectedTime / 5);
109
107
  }
110
- // eslint-disable-next-line no-param-reassign
111
108
  direction = swipeOffset > 0 ? "prev" : "next";
112
109
  }
113
110
  else {
@@ -116,7 +113,7 @@ export const Controller = ({ children, ...props }) => {
116
113
  }
117
114
  const newState = {};
118
115
  if (direction === "prev") {
119
- if (isSwipeValid(swipeOffset)) {
116
+ if (isSwipeValid(1)) {
120
117
  newState.currentIndex = (currentIndex - 1 + slidesCount) % slidesCount;
121
118
  newState.globalIndex = globalIndex - 1;
122
119
  }
@@ -126,7 +123,7 @@ export const Controller = ({ children, ...props }) => {
126
123
  }
127
124
  }
128
125
  else if (direction === "next") {
129
- if (isSwipeValid(swipeOffset)) {
126
+ if (isSwipeValid(-1)) {
130
127
  newState.currentIndex = (currentIndex + 1) % slidesCount;
131
128
  newState.globalIndex = globalIndex + 1;
132
129
  }
@@ -146,7 +143,7 @@ export const Controller = ({ children, ...props }) => {
146
143
  }, newSwipeAnimationDuration);
147
144
  }
148
145
  setState((prev) => ({ ...prev, ...newState }));
149
- }, [setTimeout, resetSwipe, isSwipeValid, rerender]);
146
+ }, [setTimeout, resetSwipe, isSwipeValid, rerender, containerRef]);
150
147
  React.useEffect(() => cleanup(subscribe("prev", () => swipe("prev")), subscribe("next", () => swipe("next"))), [subscribe, swipe]);
151
148
  React.useEffect(() => subscribeSensors("onKeyUp", (event) => {
152
149
  if (event.code === "Escape") {
@@ -205,11 +202,9 @@ export const Controller = ({ children, ...props }) => {
205
202
  const onWheel = React.useCallback((event) => {
206
203
  var _a;
207
204
  if (event.ctrlKey) {
208
- // zoom
209
205
  return;
210
206
  }
211
207
  if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
212
- // pan-y
213
208
  return;
214
209
  }
215
210
  const { current } = refs;
@@ -218,6 +213,9 @@ export const Controller = ({ children, ...props }) => {
218
213
  current.wheelResidualMomentum = event.deltaX;
219
214
  return;
220
215
  }
216
+ if (!isSwipeValid(-event.deltaX)) {
217
+ return;
218
+ }
221
219
  current.swipeIntent += event.deltaX;
222
220
  clearTimeout(current.swipeIntentCleanup);
223
221
  if (Math.abs(current.swipeIntent) > SWIPE_OFFSET_THRESHOLD) {
@@ -235,7 +233,7 @@ export const Controller = ({ children, ...props }) => {
235
233
  }
236
234
  }
237
235
  else if (current.swipeState === "swipe") {
238
- const containerWidth = (_a = current.containerRect) === null || _a === void 0 ? void 0 : _a.width;
236
+ const containerWidth = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth;
239
237
  if (containerWidth) {
240
238
  current.swipeOffset -= event.deltaX;
241
239
  current.swipeOffset =
@@ -261,19 +259,20 @@ export const Controller = ({ children, ...props }) => {
261
259
  else {
262
260
  current.wheelResidualMomentum = event.deltaX;
263
261
  }
264
- }, [updateSwipeOffset, setTimeout, clearTimeout, swipe, resetSwipe, rerender]);
262
+ }, [updateSwipeOffset, setTimeout, clearTimeout, swipe, resetSwipe, rerender, isSwipeValid, containerRef]);
265
263
  React.useEffect(() => subscribeSensors("onWheel", onWheel), [subscribeSensors, onWheel]);
266
264
  const context = React.useMemo(() => ({
265
+ latestProps,
267
266
  containerRef,
268
- containerRect,
269
267
  currentIndex: state.currentIndex,
270
268
  globalIndex: state.globalIndex,
271
269
  subscribeSensors,
272
- }), [containerRef, containerRect, state.currentIndex, state.globalIndex, subscribeSensors]);
270
+ }), [latestProps, containerRef, state.currentIndex, state.globalIndex, subscribeSensors]);
273
271
  return (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass("container"), refs.current.swipeState === "swipe" && cssClass("container_swipe")), style: refs.current.swipeAnimationDuration !== LightboxDefaultProps.animation.swipe
274
272
  ? {
275
273
  [cssVar("swipe_animation_duration")]: `${Math.round(refs.current.swipeAnimationDuration)}ms`,
276
274
  }
277
- : undefined, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors }, containerRect && (React.createElement(ControllerContext.Provider, { value: context }, children))));
275
+ : undefined, role: "presentation", "aria-live": "polite", tabIndex: -1, ...registerSensors },
276
+ React.createElement(ControllerContext.Provider, { value: context }, children)));
278
277
  };
279
278
  export const ControllerModule = createModule("controller", Controller);
@@ -7,7 +7,6 @@ const scrollbarPadding = cssVar("scrollbar_padding");
7
7
  export const NoScroll = ({ children }) => {
8
8
  React.useEffect(() => {
9
9
  const scrollbarWidth = Math.round(window.innerWidth - document.documentElement.clientWidth);
10
- // using an arbitrary threshold to counter the 1px difference in some browsers
11
10
  if (scrollbarWidth > 1) {
12
11
  document.body.style.setProperty(scrollbarPadding, `${scrollbarWidth}px`);
13
12
  document.body.classList.add(padScrollbar);
@@ -0,0 +1,22 @@
1
+ import { Plugin } from "../types.js";
2
+ declare type TextAlignment = "start" | "end" | "center";
3
+ declare module "../types.js" {
4
+ interface SlideImage {
5
+ /** slide title */
6
+ title?: string;
7
+ /** slide description */
8
+ description?: string;
9
+ }
10
+ interface LightboxProps {
11
+ /** Captions plugin settings */
12
+ captions?: {
13
+ /** description text alignment */
14
+ descriptionTextAlign?: TextAlignment;
15
+ /** maximum number of lines to display in the description section */
16
+ descriptionMaxLines?: number;
17
+ };
18
+ }
19
+ }
20
+ /** Captions plugin */
21
+ export declare const Captions: Plugin;
22
+ export default Captions;
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ import { cssClass, cssVar } from "../core/utils.js";
3
+ const defaultTextAlign = "start";
4
+ const defaultMaxLines = 3;
5
+ const cls = (className) => cssClass(`slide_${className}`);
6
+ const hasTitle = (slide) => "title" in slide ? typeof slide.title === "string" : false;
7
+ const hasDescription = (slide) => "description" in slide ? typeof slide.description === "string" : false;
8
+ const Title = ({ title }) => (React.createElement("div", { className: cls(`title_container`) },
9
+ React.createElement("div", { className: cls("title") }, title)));
10
+ const Description = ({ description, align, maxLines }) => (React.createElement("div", { className: cls("description_container") },
11
+ React.createElement("div", { className: cls("description"), ...(align !== defaultTextAlign || maxLines !== defaultMaxLines
12
+ ? {
13
+ style: {
14
+ [cssVar("slide_description_text_align")]: align,
15
+ [cssVar("slide_description_max_lines")]: maxLines,
16
+ },
17
+ }
18
+ : null) }, description.split("\n").flatMap((line, index) => [...(index > 0 ? [React.createElement("br", { key: index })] : []), line]))));
19
+ export const Captions = ({ augment }) => {
20
+ augment(({ render: { slideFooter: renderFooter, ...restRender }, captions, ...restProps }) => ({
21
+ render: {
22
+ slideFooter: (slide) => {
23
+ var _a, _b;
24
+ return (React.createElement(React.Fragment, null, renderFooter === null || renderFooter === void 0 ? void 0 :
25
+ renderFooter(slide),
26
+ hasTitle(slide) && React.createElement(Title, { title: slide.title }),
27
+ hasDescription(slide) && (React.createElement(Description, { description: slide.description, align: (_a = captions === null || captions === void 0 ? void 0 : captions.descriptionTextAlign) !== null && _a !== void 0 ? _a : defaultTextAlign, maxLines: (_b = captions === null || captions === void 0 ? void 0 : captions.descriptionMaxLines) !== null && _b !== void 0 ? _b : defaultMaxLines }))));
28
+ },
29
+ ...restRender,
30
+ },
31
+ ...restProps,
32
+ }));
33
+ };
34
+ export default Captions;
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { LightboxProps, Plugin, Render } from "../types.js";
3
3
  declare module "../types.js" {
4
4
  interface LightboxProps {
5
- /** enter fullscreen mode automatically when the lightbox opens */
5
+ /** if `true`, enter fullscreen mode automatically when the lightbox opens */
6
6
  fullscreen?: boolean;
7
7
  }
8
8
  interface Render {
@@ -2,7 +2,6 @@ import * as React from "react";
2
2
  import { createIcon, IconButton, label, useController, useLatest } from "../core/index.js";
3
3
  const EnterFullscreenIcon = createIcon("EnterFullscreen", React.createElement("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }));
4
4
  const ExitFullscreenIcon = createIcon("ExitFullscreen", React.createElement("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }));
5
- /** Fullscreen button */
6
5
  export const FullscreenButton = ({ auto, labels, render }) => {
7
6
  const [fullscreen, setFullscreen] = React.useState(false);
8
7
  const latestAuto = useLatest(auto);
@@ -33,7 +32,6 @@ export const FullscreenButton = ({ auto, labels, render }) => {
33
32
  }
34
33
  }
35
34
  catch (err) {
36
- //
37
35
  }
38
36
  }
39
37
  }, [containerRef]);
@@ -54,7 +52,6 @@ export const FullscreenButton = ({ auto, labels, render }) => {
54
52
  }
55
53
  }
56
54
  catch (err) {
57
- //
58
55
  }
59
56
  }
60
57
  }, [getFullscreenElement]);
@@ -95,7 +92,6 @@ export const FullscreenButton = ({ auto, labels, render }) => {
95
92
  return null;
96
93
  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 }));
97
94
  };
98
- /** Fullscreen plugin */
99
95
  export const Fullscreen = ({ augment }) => {
100
96
  augment(({ toolbar: { buttons, ...restToolbar }, ...restProps }) => ({
101
97
  toolbar: {
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { Component, Plugin } from "../types.js";
3
3
  declare module "../types.js" {
4
4
  interface LightboxProps {
5
- /** HTML div element attributes to be passed to inline plugin container */
5
+ /** HTML div element attributes to be passed to the inline plugin container */
6
6
  inline?: React.HTMLAttributes<HTMLDivElement>;
7
7
  }
8
8
  }
@@ -1,10 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { createModule } from "../core/index.js";
3
- /** Inline plugin container */
4
3
  export const InlineContainer = ({ inline, children }) => React.createElement("div", { ...inline }, children);
5
- /** Inline plugin module */
6
4
  export const InlineModule = createModule("inline", InlineContainer);
7
- /** Inline plugin */
8
5
  export const Inline = ({ augment, replace, remove }) => {
9
6
  augment(({ toolbar: { buttons, ...restToolbar }, open, close, controller: { focus, ...restController }, ...restProps }) => ({
10
7
  open: true,
@@ -6,11 +6,11 @@ export interface SlideVideo {
6
6
  type: "video";
7
7
  /** video placeholder image */
8
8
  poster?: string;
9
- /** video width */
9
+ /** video width in pixels */
10
10
  width?: number;
11
- /** video height */
11
+ /** video height in pixels */
12
12
  height?: number;
13
- /** vide source files */
13
+ /** an array of video files */
14
14
  sources?: {
15
15
  /** video source URL */
16
16
  src: string;
@@ -12,7 +12,6 @@ SlideTypesPropTypes.push(PropTypes.shape({
12
12
  type: PropTypes.string.isRequired,
13
13
  }).isRequired),
14
14
  }));
15
- /** Video slide */
16
15
  export const VideoSlide = ({ slide: { sources, poster, width, height } }) => {
17
16
  const { setContainerRef, containerRect } = useContainerRect();
18
17
  const scaleWidthAndHeight = () => {
@@ -27,21 +26,16 @@ export const VideoSlide = ({ slide: { sources, poster, width, height } }) => {
27
26
  return (React.createElement(React.Fragment, null, sources && (React.createElement("div", { ref: setContainerRef, style: {
28
27
  width: "100%",
29
28
  height: "100%",
30
- }, className: clsx(cssClass("video_container"), cssClass("flex_center")) }, containerRect && (
31
- // eslint-disable-next-line jsx-a11y/media-has-caption
32
- React.createElement("video", { controls: true, playsInline: true, poster: poster, ...scaleWidthAndHeight() }, sources.map(({ src, type }, index) => (
33
- // eslint-disable-next-line react/no-array-index-key
34
- React.createElement("source", { key: index, src: src, type: type })))))))));
29
+ }, className: clsx(cssClass("video_container"), cssClass("flex_center")) }, containerRect && (React.createElement("video", { controls: true, playsInline: true, poster: poster, ...scaleWidthAndHeight() }, sources.map(({ src, type }, index) => (React.createElement("source", { key: index, src: src, type: type })))))))));
35
30
  };
36
- /** Video plugin */
37
31
  export const Video = ({ augment }) => {
38
32
  augment(({ render: { slide: renderSlide, ...restRender }, ...restProps }) => ({
39
33
  render: {
40
- slide: (slide) => {
34
+ slide: (slide, offset, rect) => {
41
35
  if ("type" in slide && slide.type === "video") {
42
36
  return React.createElement(VideoSlide, { slide: slide });
43
37
  }
44
- return renderSlide === null || renderSlide === void 0 ? void 0 : renderSlide(slide);
38
+ return renderSlide === null || renderSlide === void 0 ? void 0 : renderSlide(slide, offset, rect);
45
39
  },
46
40
  ...restRender,
47
41
  },
@@ -0,0 +1,39 @@
1
+ .yarl__slide_title {
2
+ font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
3
+ font-size: 1.25rem;
4
+ font-weight: 700;
5
+ max-width: calc(100% - 96px - 8px);
6
+ overflow: hidden;
7
+ text-overflow: ellipsis;
8
+ white-space: nowrap;
9
+ }
10
+ .yarl__slide_title_container {
11
+ position: absolute;
12
+ left: 0;
13
+ right: 0;
14
+ top: 0;
15
+ padding: 16px;
16
+ background: rgba(0, 0, 0, 0.5);
17
+ }
18
+ .yarl__slide_description {
19
+ font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
20
+ font-size: 1rem;
21
+ line-height: 1.2;
22
+ font-weight: 500;
23
+ overflow: hidden;
24
+ -webkit-hyphens: auto;
25
+ -ms-hyphens: auto;
26
+ hyphens: auto;
27
+ display: -webkit-box;
28
+ -webkit-box-orient: vertical;
29
+ -webkit-line-clamp: var(--yarl__slide_description_max_lines, 3);
30
+ text-align: var(--yarl__slide_description_text_align, start);
31
+ }
32
+ .yarl__slide_description_container {
33
+ position: absolute;
34
+ left: 0;
35
+ right: 0;
36
+ bottom: 0;
37
+ padding: 16px;
38
+ background: rgba(0, 0, 0, 0.5);
39
+ }
@@ -1,3 +1,4 @@
1
+ export * from "./Captions.js";
1
2
  export * from "./Fullscreen.js";
2
3
  export * from "./Inline.js";
3
4
  export * from "./Video.js";
@@ -1,3 +1,4 @@
1
+ export * from "./Captions.js";
1
2
  export * from "./Fullscreen.js";
2
3
  export * from "./Inline.js";
3
4
  export * from "./Video.js";
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import PropTypes from "prop-types";
3
+ import { ContainerRect } from "./core/hooks/useContainerRect.js";
3
4
  /** Image slide properties */
4
5
  export interface SlideImage {
5
6
  /** image URL */
@@ -49,7 +50,19 @@ export interface ControllerSettings {
49
50
  /** Custom render functions. */
50
51
  export interface Render {
51
52
  /** render custom slide type, or override the default image slide */
52
- slide?: (slide: Slide) => React.ReactNode;
53
+ slide?: (
54
+ /** slide */
55
+ slide: Slide,
56
+ /** slide offset (`0` - current slide, `1` - next slide, `-1` - previous slide, etc.) */
57
+ offset: number,
58
+ /** container rect */
59
+ rect: ContainerRect) => React.ReactNode;
60
+ /** render custom slide header */
61
+ slideHeader?: (slide: Slide) => React.ReactNode;
62
+ /** render custom slide footer */
63
+ slideFooter?: (slide: Slide) => React.ReactNode;
64
+ /** render custom slide container */
65
+ slideContainer?: (slide: Slide, children: React.ReactNode) => React.ReactNode;
53
66
  /** render custom Prev icon */
54
67
  iconPrev?: () => React.ReactNode;
55
68
  /** render custom Next icon */
@@ -110,6 +123,15 @@ export interface LightboxProps {
110
123
  /** lifecycle callbacks */
111
124
  on: Callbacks;
112
125
  }
126
+ export declare const ImageSlidePropTypes: PropTypes.Requireable<PropTypes.InferProps<{
127
+ src: PropTypes.Requireable<string>;
128
+ alt: PropTypes.Requireable<string>;
129
+ aspectRatio: PropTypes.Requireable<number>;
130
+ srcSet: PropTypes.Requireable<PropTypes.InferProps<{
131
+ src: PropTypes.Validator<string>;
132
+ width: PropTypes.Validator<number>;
133
+ }>[]>;
134
+ }>>;
113
135
  export declare const SlideTypesPropTypes: PropTypes.Validator<any>[];
114
136
  export declare const LightboxPropTypes: {
115
137
  open: PropTypes.Validator<boolean>;
@@ -118,6 +140,9 @@ export declare const LightboxPropTypes: {
118
140
  slides: PropTypes.Validator<any[]>;
119
141
  render: PropTypes.Validator<PropTypes.InferProps<{
120
142
  slide: PropTypes.Requireable<(...args: any[]) => any>;
143
+ slideHeader: PropTypes.Requireable<(...args: any[]) => any>;
144
+ slideFooter: PropTypes.Requireable<(...args: any[]) => any>;
145
+ slideContainer: PropTypes.Requireable<(...args: any[]) => any>;
121
146
  iconPrev: PropTypes.Requireable<(...args: any[]) => any>;
122
147
  iconNext: PropTypes.Requireable<(...args: any[]) => any>;
123
148
  iconClose: PropTypes.Requireable<(...args: any[]) => any>;
package/dist/types.js CHANGED
@@ -1,18 +1,14 @@
1
1
  import PropTypes from "prop-types";
2
- export const SlideTypesPropTypes = [
3
- PropTypes.shape({
2
+ export const ImageSlidePropTypes = PropTypes.shape({
3
+ src: PropTypes.string,
4
+ alt: PropTypes.string,
5
+ aspectRatio: PropTypes.number,
6
+ srcSet: PropTypes.arrayOf(PropTypes.shape({
4
7
  src: PropTypes.string.isRequired,
5
- title: PropTypes.string,
6
- aspectRatio: PropTypes.number,
7
- srcSet: PropTypes.oneOfType([
8
- PropTypes.string.isRequired,
9
- PropTypes.arrayOf(PropTypes.shape({
10
- src: PropTypes.string.isRequired,
11
- width: PropTypes.number.isRequired,
12
- }).isRequired).isRequired,
13
- ]),
14
- }),
15
- ];
8
+ width: PropTypes.number.isRequired,
9
+ }).isRequired),
10
+ });
11
+ export const SlideTypesPropTypes = [ImageSlidePropTypes];
16
12
  export const LightboxPropTypes = {
17
13
  open: PropTypes.bool.isRequired,
18
14
  close: PropTypes.func.isRequired,
@@ -20,6 +16,9 @@ export const LightboxPropTypes = {
20
16
  slides: PropTypes.arrayOf(PropTypes.oneOfType(SlideTypesPropTypes).isRequired).isRequired,
21
17
  render: PropTypes.shape({
22
18
  slide: PropTypes.func,
19
+ slideHeader: PropTypes.func,
20
+ slideFooter: PropTypes.func,
21
+ slideContainer: PropTypes.func,
23
22
  iconPrev: PropTypes.func,
24
23
  iconNext: PropTypes.func,
25
24
  iconClose: PropTypes.func,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "Modern React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",
@@ -10,6 +10,8 @@
10
10
  ".": "./dist/index.js",
11
11
  "./core": "./dist/core/index.js",
12
12
  "./plugins": "./dist/plugins/index.js",
13
+ "./plugins/captions": "./dist/plugins/Captions.js",
14
+ "./plugins/captions.css": "./dist/plugins/captions.css",
13
15
  "./plugins/fullscreen": "./dist/plugins/Fullscreen.js",
14
16
  "./plugins/inline": "./dist/plugins/Inline.js",
15
17
  "./plugins/video": "./dist/plugins/Video.js",
@@ -27,6 +29,9 @@
27
29
  "plugins": [
28
30
  "dist/plugins/index.d.ts"
29
31
  ],
32
+ "plugins/captions": [
33
+ "dist/plugins/Captions.d.ts"
34
+ ],
30
35
  "plugins/fullscreen": [
31
36
  "dist/plugins/Fullscreen.d.ts"
32
37
  ],
@@ -59,8 +64,8 @@
59
64
  "build": "npm-run-all clean build:scss build:css build:js build:dts",
60
65
  "build:js": "tsc -p tsconfig.build.js.json",
61
66
  "build:dts": "tsc -p tsconfig.build.dts.json",
62
- "build:css": "postcss src/styles.css -u autoprefixer --no-map -d dist",
63
- "build:scss": "sass src/styles.scss src/styles.css --no-source-map ",
67
+ "build:css": "postcss src/*.css src/**/*.css --base src -d dist -u autoprefixer --no-map",
68
+ "build:scss": "sass src --no-source-map",
64
69
  "start": "npm-run-all clean --parallel \"build:* -- -w\"",
65
70
  "lint": "eslint .",
66
71
  "test": "jest"
@@ -80,13 +85,13 @@
80
85
  "@testing-library/jest-dom": "^5.16.4",
81
86
  "@testing-library/react": "^13.3.0",
82
87
  "@testing-library/user-event": "^14.2.0",
83
- "@types/jest": "^27.5.1",
88
+ "@types/jest": "^28.1.0",
84
89
  "@types/react": "^18.0.10",
85
90
  "@types/react-dom": "^18.0.5",
86
91
  "@typescript-eslint/eslint-plugin": "^5.27.0",
87
92
  "@typescript-eslint/parser": "^5.27.0",
88
93
  "autoprefixer": "^10.4.7",
89
- "eslint": "^8.16.0",
94
+ "eslint": "^8.17.0",
90
95
  "eslint-config-airbnb": "^19.0.4",
91
96
  "eslint-config-airbnb-typescript": "^17.0.0",
92
97
  "eslint-config-prettier": "^8.5.0",
@@ -106,9 +111,9 @@
106
111
  "react": "^18.1.0",
107
112
  "react-dom": "^18.1.0",
108
113
  "rimraf": "^3.0.2",
109
- "sass": "^1.52.1",
110
- "ts-jest": "^28.0.3",
111
- "typescript": "^4.7.2"
114
+ "sass": "^1.52.2",
115
+ "ts-jest": "^28.0.4",
116
+ "typescript": "^4.7.3"
112
117
  },
113
118
  "keywords": [
114
119
  "react",