yet-another-react-lightbox 2.0.0-rc.3 → 2.0.0-rc.4

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.
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import * as React from "react";
2
2
  import { ImageFit, Render, SlideImage } from "../../types.js";
3
3
  import { ContainerRect } from "../hooks/index.js";
4
4
  export declare type ImageSlideProps = {
@@ -8,5 +8,7 @@ export declare type ImageSlideProps = {
8
8
  rect?: ContainerRect;
9
9
  imageFit?: ImageFit;
10
10
  onClick?: () => void;
11
+ onLoad?: (image: HTMLImageElement) => void;
12
+ style?: React.CSSProperties;
11
13
  };
12
- export declare const ImageSlide: ({ slide: image, offset, render, rect, imageFit, onClick }: ImageSlideProps) => JSX.Element;
14
+ export declare const ImageSlide: ({ slide: image, offset, render, rect, imageFit, onClick, onLoad, style, }: ImageSlideProps) => JSX.Element;
@@ -6,7 +6,7 @@ import { ErrorIcon, LoadingIcon } from "./Icons.js";
6
6
  import { activeSlideStatus, ELEMENT_ICON, IMAGE_FIT_CONTAIN, IMAGE_FIT_COVER, SLIDE_STATUS_COMPLETE, SLIDE_STATUS_ERROR, SLIDE_STATUS_LOADING, SLIDE_STATUS_PLACEHOLDER, } from "../consts.js";
7
7
  const slidePrefix = makeComposePrefix("slide");
8
8
  const slideImagePrefix = makeComposePrefix("slide_image");
9
- export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onClick }) => {
9
+ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onClick, onLoad, style, }) => {
10
10
  var _a, _b, _c, _d, _e, _f, _g;
11
11
  const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
12
12
  const { publish } = useEvents();
@@ -27,6 +27,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onCli
27
27
  return;
28
28
  }
29
29
  setStatus(SLIDE_STATUS_COMPLETE);
30
+ onLoad === null || onLoad === void 0 ? void 0 : onLoad(img);
30
31
  });
31
32
  });
32
33
  const setImageRef = React.useCallback((img) => {
@@ -35,7 +36,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onCli
35
36
  handleLoading(img);
36
37
  }
37
38
  }, [handleLoading]);
38
- const onLoad = React.useCallback((event) => {
39
+ const handleOnLoad = React.useCallback((event) => {
39
40
  handleLoading(event.currentTarget);
40
41
  }, [handleLoading]);
41
42
  const onError = React.useCallback(() => {
@@ -45,7 +46,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onCli
45
46
  const nonInfinite = (value, fallback) => (Number.isFinite(value) ? value : fallback);
46
47
  const maxWidth = 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);
47
48
  const maxHeight = nonInfinite(Math.max(...((_e = (_d = image.srcSet) === null || _d === void 0 ? void 0 : _d.map((x) => x.height)) !== null && _e !== void 0 ? _e : []).concat(image.height ? [image.height] : [])), ((_f = imageRef.current) === null || _f === void 0 ? void 0 : _f.naturalHeight) || 0);
48
- const style = maxWidth && maxHeight
49
+ const defaultStyle = maxWidth && maxHeight
49
50
  ? {
50
51
  maxWidth: `min(${maxWidth}px, 100%)`,
51
52
  maxHeight: `min(${maxHeight}px, 100%)`,
@@ -58,7 +59,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onCli
58
59
  const estimateActualWidth = () => rect && !cover && image.width && image.height ? (rect.height / image.height) * image.width : Number.MAX_VALUE;
59
60
  const sizes = srcSet && rect && hasWindow() ? `${Math.round(Math.min(estimateActualWidth(), rect.width))}px` : undefined;
60
61
  return (React.createElement(React.Fragment, null,
61
- React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, onClick: onClick, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading"))), draggable: false, alt: image.alt, style: style, sizes: sizes, srcSet: srcSet, src: image.src }),
62
+ React.createElement("img", { ref: setImageRef, onLoad: handleOnLoad, onError: onError, onClick: onClick, className: clsx(cssClass(slideImagePrefix()), cover && cssClass(slideImagePrefix("cover")), status !== SLIDE_STATUS_COMPLETE && cssClass(slideImagePrefix("loading"))), draggable: false, alt: image.alt, style: { ...defaultStyle, ...style }, sizes: sizes, srcSet: srcSet, src: image.src }),
62
63
  status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass(slidePrefix(SLIDE_STATUS_PLACEHOLDER)) },
63
64
  status === SLIDE_STATUS_LOADING &&
64
65
  ((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass(ELEMENT_ICON), cssClass(slidePrefix(SLIDE_STATUS_LOADING))) }))),
@@ -12,7 +12,7 @@ export const Controller = ({ children, ...props }) => {
12
12
  const { carousel, slides, animation, controller, on, styles } = props;
13
13
  const { state, dispatch } = useLightboxState();
14
14
  const [swipeState, setSwipeState] = React.useState(SwipeState.NONE);
15
- const [swipeOffset, setSwipeOffset] = React.useState(0);
15
+ const swipeOffset = React.useRef(0);
16
16
  const swipeAnimationReset = React.useRef();
17
17
  const { registerSensors, subscribeSensors } = useSensors();
18
18
  const { subscribe, publish } = useEvents();
@@ -29,6 +29,11 @@ export const Controller = ({ children, ...props }) => {
29
29
  const isSwipeValid = useEventCallback((offset) => !(carousel.finite &&
30
30
  ((rtl(offset) > 0 && state.currentIndex === 0) ||
31
31
  (rtl(offset) < 0 && state.currentIndex === slides.length - 1))));
32
+ const setSwipeOffset = React.useCallback((offset) => {
33
+ var _a;
34
+ swipeOffset.current = offset;
35
+ (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.style.setProperty(cssVar("swipe_offset"), `${Math.round(offset)}px`);
36
+ }, [containerRef]);
32
37
  const swipe = useEventCallback((action) => {
33
38
  var _a;
34
39
  const swipeDuration = animation.swipe;
@@ -79,6 +84,12 @@ export const Controller = ({ children, ...props }) => {
79
84
  newSwipeAnimationDuration = swipeDuration;
80
85
  }
81
86
  }
87
+ if (carouselRef.current) {
88
+ carouselSwipeAnimation.current = {
89
+ rect: carouselRef.current.getBoundingClientRect(),
90
+ index: state.globalIndex,
91
+ };
92
+ }
82
93
  newSwipeAnimationDuration = Math.round(newSwipeAnimationDuration);
83
94
  clearTimeout(swipeAnimationReset.current);
84
95
  if (newSwipeState) {
@@ -90,12 +101,6 @@ export const Controller = ({ children, ...props }) => {
90
101
  }, newSwipeAnimationDuration);
91
102
  swipeAnimationReset.current = timeoutId;
92
103
  }
93
- if (carouselRef.current) {
94
- carouselSwipeAnimation.current = {
95
- rect: carouselRef.current.getBoundingClientRect(),
96
- index: state.globalIndex,
97
- };
98
- }
99
104
  setSwipeState(newSwipeState);
100
105
  dispatch({ increment, animationDuration: newSwipeAnimationDuration });
101
106
  });
@@ -125,22 +130,17 @@ export const Controller = ({ children, ...props }) => {
125
130
  });
126
131
  useLayoutEffect(animateCarouselSwipe);
127
132
  const swipeParams = [
128
- swipeState,
129
133
  subscribeSensors,
130
134
  isSwipeValid,
131
135
  (containerRect === null || containerRect === void 0 ? void 0 : containerRect.width) || 0,
132
136
  animation.swipe,
133
137
  () => setSwipeState(SwipeState.SWIPE),
138
+ (offset) => setSwipeOffset(offset),
134
139
  (offset, duration) => swipe({ offset, duration, count: 1 }),
135
140
  (offset) => swipe({ offset, count: 0 }),
136
- (offset) => {
137
- if (swipeState === SwipeState.SWIPE) {
138
- setSwipeOffset(offset);
139
- }
140
- },
141
141
  ];
142
142
  usePointerSwipe(...swipeParams);
143
- useWheelSwipe(...swipeParams);
143
+ useWheelSwipe(swipeState, ...swipeParams);
144
144
  const focusOnMount = useEventCallback(() => {
145
145
  var _a;
146
146
  if (controller.focus) {
@@ -180,7 +180,7 @@ export const Controller = ({ children, ...props }) => {
180
180
  }), [getLightboxProps, subscribeSensors, transferFocus, containerRect, containerRef, setCarouselRef]);
181
181
  return (React.createElement("div", { ref: handleContainerRef, className: clsx(cssClass(cssContainerPrefix()), cssClass(CLASS_FLEX_CENTER)), style: {
182
182
  ...(swipeState === SwipeState.SWIPE
183
- ? { [cssVar("swipe_offset")]: `${Math.round(swipeOffset)}px` }
183
+ ? { [cssVar("swipe_offset")]: `${Math.round(swipeOffset.current)}px` }
184
184
  : null),
185
185
  ...(controller.touchAction !== "none" ? {} : null),
186
186
  ...styles.container,
@@ -1,3 +1,2 @@
1
1
  import { UseSensors } from "../../hooks/useSensors.js";
2
- import { SwipeState } from "./index.js";
3
- export declare const usePointerSwipe: <T extends Element = Element>(swipeState: SwipeState, subscribeSensors: import("../../hooks/useSensors.js").SubscribeSensors<T>, isSwipeValid: (offset: number) => boolean, containerWidth: number, swipeAnimationDuration: number, onSwipeStart: () => void, onSwipeFinish: (offset: number, duration: number) => void, onSwipeCancel: (offset: number) => void, onChange: (offset: number) => void) => void;
2
+ export declare const usePointerSwipe: <T extends Element = Element>(subscribeSensors: import("../../hooks/useSensors.js").SubscribeSensors<T>, isSwipeValid: (offset: number) => boolean, containerWidth: number, swipeAnimationDuration: number, onSwipeStart: () => void, onSwipeProgress: (offset: number) => void, onSwipeFinish: (offset: number, duration: number) => void, onSwipeCancel: (offset: number) => void) => void;
@@ -1,11 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { cleanup } from "../../utils.js";
3
3
  import { useEventCallback } from "../../hooks/useEventCallback.js";
4
- import { SwipeState } from "./index.js";
5
- import { useOffset } from "./useOffset.js";
6
4
  import { EVENT_ON_POINTER_CANCEL, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, } from "../../consts.js";
7
- export const usePointerSwipe = (swipeState, subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeFinish, onSwipeCancel, onChange) => {
8
- const [offset, setOffset] = useOffset(onChange);
5
+ export const usePointerSwipe = (subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeProgress, onSwipeFinish, onSwipeCancel) => {
6
+ const offset = React.useRef(0);
9
7
  const pointers = React.useRef([]);
10
8
  const activePointer = React.useRef();
11
9
  const startTime = React.useRef(0);
@@ -24,18 +22,18 @@ export const usePointerSwipe = (swipeState, subscribeSensors, isSwipeValid, cont
24
22
  addPointer(event);
25
23
  });
26
24
  const onPointerUp = useEventCallback((event) => {
27
- if (swipeState === SwipeState.SWIPE &&
28
- pointers.current.find((x) => x.pointerId === event.pointerId) &&
25
+ if (pointers.current.find((x) => x.pointerId === event.pointerId) &&
29
26
  activePointer.current === event.pointerId) {
30
27
  const duration = Date.now() - startTime.current;
31
- if (Math.abs(offset) > 0.3 * containerWidth ||
32
- (Math.abs(offset) > 5 && duration < swipeAnimationDuration)) {
33
- onSwipeFinish(offset, duration);
28
+ const currentOffset = offset.current;
29
+ if (Math.abs(currentOffset) > 0.3 * containerWidth ||
30
+ (Math.abs(currentOffset) > 5 && duration < swipeAnimationDuration)) {
31
+ onSwipeFinish(currentOffset, duration);
34
32
  }
35
33
  else {
36
- onSwipeCancel(offset);
34
+ onSwipeCancel(currentOffset);
37
35
  }
38
- setOffset(0);
36
+ offset.current = 0;
39
37
  }
40
38
  clearPointer(event);
41
39
  });
@@ -44,16 +42,18 @@ export const usePointerSwipe = (swipeState, subscribeSensors, isSwipeValid, cont
44
42
  if (pointer) {
45
43
  const deltaX = event.clientX - pointer.clientX;
46
44
  const deltaY = event.clientY - pointer.clientY;
47
- if (!swipeState) {
48
- if (isSwipeValid(deltaX) && Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 30) {
49
- addPointer(event);
50
- activePointer.current = event.pointerId;
51
- startTime.current = Date.now();
52
- onSwipeStart();
53
- }
45
+ if (!activePointer.current &&
46
+ isSwipeValid(deltaX) &&
47
+ Math.abs(deltaX) > Math.abs(deltaY) &&
48
+ Math.abs(deltaX) > 30) {
49
+ addPointer(event);
50
+ activePointer.current = event.pointerId;
51
+ startTime.current = Date.now();
52
+ onSwipeStart();
54
53
  }
55
54
  else if (activePointer.current === event.pointerId) {
56
- setOffset(deltaX);
55
+ offset.current = deltaX;
56
+ onSwipeProgress(deltaX);
57
57
  }
58
58
  }
59
59
  });
@@ -1,3 +1,3 @@
1
1
  import { UseSensors } from "../../hooks/index.js";
2
2
  import { SwipeState } from "./index.js";
3
- export declare const useWheelSwipe: <T extends Element = Element>(swipeState: SwipeState | undefined, subscribeSensors: import("../../hooks/useSensors.js").SubscribeSensors<T>, isSwipeValid: (offset: number) => boolean, containerWidth: number, swipeAnimationDuration: number, onSwipeStart: () => void, onSwipeFinish: (offset: number, duration: number) => void, onSwipeCancel: (offset: number) => void, onChange: (offset: number) => void) => void;
3
+ export declare const useWheelSwipe: <T extends Element = Element>(swipeState: SwipeState, subscribeSensors: import("../../hooks/useSensors.js").SubscribeSensors<T>, isSwipeValid: (offset: number) => boolean, containerWidth: number, swipeAnimationDuration: number, onSwipeStart: () => void, onSwipeProgress: (offset: number) => void, onSwipeFinish: (offset: number, duration: number) => void, onSwipeCancel: (offset: number) => void) => void;
@@ -2,33 +2,30 @@ import * as React from "react";
2
2
  import { useEventCallback } from "../../hooks/index.js";
3
3
  import { useTimeouts } from "../../contexts/index.js";
4
4
  import { SwipeState } from "./index.js";
5
- import { useOffset } from "./useOffset.js";
6
5
  import { EVENT_ON_WHEEL } from "../../consts.js";
7
- export const useWheelSwipe = (swipeState, subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeFinish, onSwipeCancel, onChange) => {
8
- const [offset, setOffset] = useOffset(onChange);
9
- const swipeIntent = React.useRef(0);
10
- const swipeIntentCleanup = React.useRef();
11
- const swipeResetCleanup = React.useRef();
6
+ export const useWheelSwipe = (swipeState, subscribeSensors, isSwipeValid, containerWidth, swipeAnimationDuration, onSwipeStart, onSwipeProgress, onSwipeFinish, onSwipeCancel) => {
7
+ const offset = React.useRef(0);
8
+ const intent = React.useRef(0);
9
+ const intentCleanup = React.useRef();
10
+ const resetCleanup = React.useRef();
12
11
  const wheelResidualMomentum = React.useRef(0);
13
12
  const startTime = React.useRef(0);
14
13
  const { setTimeout, clearTimeout } = useTimeouts();
15
14
  const cancelSwipeIntentCleanup = React.useCallback(() => {
16
- if (swipeIntentCleanup.current) {
17
- clearTimeout(swipeIntentCleanup.current);
18
- swipeIntentCleanup.current = undefined;
15
+ if (intentCleanup.current) {
16
+ clearTimeout(intentCleanup.current);
17
+ intentCleanup.current = undefined;
19
18
  }
20
19
  }, [clearTimeout]);
21
20
  const cancelSwipeResetCleanup = React.useCallback(() => {
22
- if (swipeResetCleanup.current) {
23
- clearTimeout(swipeResetCleanup.current);
24
- swipeResetCleanup.current = undefined;
21
+ if (resetCleanup.current) {
22
+ clearTimeout(resetCleanup.current);
23
+ resetCleanup.current = undefined;
25
24
  }
26
25
  }, [clearTimeout]);
27
26
  const handleCleanup = useEventCallback(() => {
28
27
  if (swipeState !== SwipeState.SWIPE) {
29
- if (offset !== 0) {
30
- setOffset(0);
31
- }
28
+ offset.current = 0;
32
29
  startTime.current = 0;
33
30
  cancelSwipeIntentCleanup();
34
31
  cancelSwipeResetCleanup();
@@ -36,9 +33,9 @@ export const useWheelSwipe = (swipeState, subscribeSensors, isSwipeValid, contai
36
33
  });
37
34
  React.useEffect(handleCleanup, [swipeState, handleCleanup]);
38
35
  const handleCancelSwipe = useEventCallback((currentSwipeOffset) => {
39
- swipeResetCleanup.current = undefined;
40
- if (swipeState === SwipeState.SWIPE && offset === currentSwipeOffset) {
41
- onSwipeCancel(offset);
36
+ resetCleanup.current = undefined;
37
+ if (offset.current === currentSwipeOffset) {
38
+ onSwipeCancel(offset.current);
42
39
  }
43
40
  });
44
41
  const onWheel = useEventCallback((event) => {
@@ -56,35 +53,36 @@ export const useWheelSwipe = (swipeState, subscribeSensors, isSwipeValid, contai
56
53
  if (!isSwipeValid(-event.deltaX)) {
57
54
  return;
58
55
  }
59
- swipeIntent.current += event.deltaX;
56
+ intent.current += event.deltaX;
60
57
  cancelSwipeIntentCleanup();
61
- if (Math.abs(swipeIntent.current) > 30) {
62
- swipeIntent.current = 0;
58
+ if (Math.abs(intent.current) > 30) {
59
+ intent.current = 0;
63
60
  wheelResidualMomentum.current = 0;
64
61
  startTime.current = Date.now();
65
62
  onSwipeStart();
66
63
  }
67
64
  else {
68
- const currentSwipeIntent = swipeIntent.current;
69
- swipeIntentCleanup.current = setTimeout(() => {
70
- swipeIntentCleanup.current = undefined;
71
- if (currentSwipeIntent === swipeIntent.current) {
72
- swipeIntent.current = 0;
65
+ const currentSwipeIntent = intent.current;
66
+ intentCleanup.current = setTimeout(() => {
67
+ intentCleanup.current = undefined;
68
+ if (currentSwipeIntent === intent.current) {
69
+ intent.current = 0;
73
70
  }
74
71
  }, swipeAnimationDuration);
75
72
  }
76
73
  }
77
74
  else if (swipeState === SwipeState.SWIPE) {
78
- let newSwipeOffset = offset - event.deltaX;
75
+ let newSwipeOffset = offset.current - event.deltaX;
79
76
  newSwipeOffset = Math.min(Math.abs(newSwipeOffset), containerWidth) * Math.sign(newSwipeOffset);
80
- setOffset(newSwipeOffset);
77
+ offset.current = newSwipeOffset;
78
+ onSwipeProgress(newSwipeOffset);
81
79
  cancelSwipeResetCleanup();
82
80
  if (Math.abs(newSwipeOffset) > 0.2 * containerWidth) {
83
81
  wheelResidualMomentum.current = event.deltaX;
84
82
  onSwipeFinish(newSwipeOffset, Date.now() - startTime.current);
85
83
  return;
86
84
  }
87
- swipeResetCleanup.current = setTimeout(() => handleCancelSwipe(newSwipeOffset), 2 * swipeAnimationDuration);
85
+ resetCleanup.current = setTimeout(() => handleCancelSwipe(newSwipeOffset), 2 * swipeAnimationDuration);
88
86
  }
89
87
  else {
90
88
  wheelResidualMomentum.current = event.deltaX;
@@ -20,3 +20,4 @@ export declare const parseLengthPercentage: (input: unknown) => {
20
20
  percent: number;
21
21
  pixel?: undefined;
22
22
  };
23
+ export declare const devicePixelRatio: () => number;
@@ -36,3 +36,6 @@ export const parseLengthPercentage = (input) => {
36
36
  }
37
37
  return { pixel: 0 };
38
38
  };
39
+ export const devicePixelRatio = () => {
40
+ return (typeof window !== "undefined" ? window === null || window === void 0 ? void 0 : window.devicePixelRatio : undefined) || 1;
41
+ };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import { ImageSlideProps } from "../../core/index.js";
3
+ import { ImageSource, SlideImage } from "../../types.js";
4
+ declare type ResponsiveImageSlide = Omit<SlideImage, "srcSet"> & {
5
+ srcSet: [ImageSource, ...ImageSource[]];
6
+ };
7
+ export declare const isResponsiveImageSlide: (slide: SlideImage) => slide is ResponsiveImageSlide;
8
+ declare type ResponsiveImageProps = Omit<ImageSlideProps, "slide" | "rect"> & Required<Pick<ImageSlideProps, "rect">> & {
9
+ slide: ResponsiveImageSlide;
10
+ };
11
+ export declare const ResponsiveImage: React.FC<ResponsiveImageProps>;
12
+ export {};
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ import { devicePixelRatio, IMAGE_FIT_CONTAIN, IMAGE_FIT_COVER, ImageSlide, useEventCallback, useLayoutEffect, } from "../../core/index.js";
3
+ export const isResponsiveImageSlide = (slide) => { var _a; return (((_a = slide.srcSet) === null || _a === void 0 ? void 0 : _a.length) || 0) > 0; };
4
+ export const ResponsiveImage = (props) => {
5
+ var _a, _b;
6
+ const [state, setState] = React.useState({});
7
+ const { current, preload } = state;
8
+ const { slide: image, rect, imageFit, render } = props;
9
+ const srcSet = image.srcSet.sort((a, b) => a.width - b.width);
10
+ const width = (_a = image.width) !== null && _a !== void 0 ? _a : srcSet[srcSet.length - 1].width;
11
+ const height = (_b = image.height) !== null && _b !== void 0 ? _b : srcSet[srcSet.length - 1].height;
12
+ const cover = image.imageFit === IMAGE_FIT_COVER || (image.imageFit !== IMAGE_FIT_CONTAIN && imageFit === IMAGE_FIT_COVER);
13
+ const maxWidth = Math.max(...srcSet.map((x) => x.width));
14
+ const targetWidth = Math.min((cover ? Math.max : Math.min)(rect.width, width * (rect.height / height)), maxWidth);
15
+ const pixelDensity = devicePixelRatio();
16
+ const handleSourceChange = useEventCallback(() => {
17
+ var _a;
18
+ const targetSource = (_a = srcSet.find((x) => x.width >= targetWidth * pixelDensity)) !== null && _a !== void 0 ? _a : srcSet[srcSet.length - 1];
19
+ if (!current) {
20
+ setState((prev) => ({ ...prev, current: targetSource.src }));
21
+ }
22
+ else if (srcSet.findIndex((x) => x.src === current) < srcSet.findIndex((x) => x === targetSource)) {
23
+ setState((prev) => ({ ...prev, preload: targetSource.src }));
24
+ }
25
+ });
26
+ useLayoutEffect(handleSourceChange, [rect === null || rect === void 0 ? void 0 : rect.width, rect === null || rect === void 0 ? void 0 : rect.height, pixelDensity, handleSourceChange]);
27
+ const handlePreload = useEventCallback((currentPreload) => {
28
+ if (currentPreload === preload) {
29
+ setState((prev) => ({ ...prev, current: preload, preload: undefined }));
30
+ }
31
+ });
32
+ const style = {
33
+ "-webkit-transform": "translateZ(0)",
34
+ ...(rect.width / rect.height < width / height
35
+ ? {
36
+ width: "100%",
37
+ height: "auto",
38
+ }
39
+ : { width: "auto", height: "100%" }),
40
+ };
41
+ return (React.createElement(React.Fragment, null,
42
+ preload && preload !== current && (React.createElement(ImageSlide, { key: preload, ...props, slide: { ...image, src: preload, srcSet: undefined }, style: { position: "absolute", visibility: "hidden", ...style }, onLoad: () => handlePreload(preload), render: {
43
+ ...render,
44
+ iconLoading: () => null,
45
+ iconError: () => null,
46
+ } })),
47
+ current && (React.createElement(ImageSlide, { key: current, ...props, slide: { ...image, src: current, srcSet: undefined }, style: style }))));
48
+ };
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
- import { createModule, MODULE_CONTROLLER, PLUGIN_ZOOM } from "../../core/index.js";
2
+ import { createModule, isImageSlide, MODULE_CONTROLLER, PLUGIN_ZOOM } from "../../core/index.js";
3
3
  import { ZoomContextProvider } from "./ZoomContext.js";
4
4
  import { ZoomButtonsGroup } from "./ZoomButtonsGroup.js";
5
- import { ZoomWrapper } from "./ZoomWrapper.js";
5
+ import { ZoomContainer } from "./ZoomContainer.js";
6
6
  export const defaultZoomProps = {
7
7
  maxZoomPixelRatio: 1,
8
8
  zoomInMultiplier: 2,
@@ -15,14 +15,17 @@ export const defaultZoomProps = {
15
15
  scrollToZoom: false,
16
16
  };
17
17
  export const Zoom = ({ augment, append }) => {
18
- augment(({ toolbar: { buttons, ...restToolbar }, render, carousel, animation, zoom, ...restProps }) => ({
18
+ augment(({ toolbar: { buttons, ...restToolbar }, render, carousel, animation, zoom, on, ...restProps }) => ({
19
19
  toolbar: {
20
20
  buttons: [React.createElement(ZoomButtonsGroup, { key: PLUGIN_ZOOM, labels: restProps.labels, render: render }), ...buttons],
21
21
  ...restToolbar,
22
22
  },
23
23
  render: {
24
24
  ...render,
25
- slide: (slide, offset, rect) => (React.createElement(ZoomWrapper, { slide: slide, offset: offset, rect: rect, render: render, carousel: carousel, animation: animation, zoom: zoom })),
25
+ slide: (slide, offset, rect) => {
26
+ var _a;
27
+ return isImageSlide(slide) ? (React.createElement(ZoomContainer, { slide: slide, offset: offset, rect: rect, render: render, carousel: carousel, animation: animation, zoom: zoom, on: on })) : ((_a = render.slide) === null || _a === void 0 ? void 0 : _a.call(render, slide, offset, rect));
28
+ },
26
29
  },
27
30
  zoom: {
28
31
  ...defaultZoomProps,
@@ -30,6 +33,7 @@ export const Zoom = ({ augment, append }) => {
30
33
  },
31
34
  carousel,
32
35
  animation,
36
+ on,
33
37
  ...restProps,
34
38
  }));
35
39
  append(MODULE_CONTROLLER, createModule(PLUGIN_ZOOM, ZoomContextProvider));
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { ContainerRect } from "../../core/index.js";
3
3
  import { LightboxProps, Slide } from "../../types.js";
4
4
  /** Zoom container */
5
- export declare const ZoomContainer: React.FC<Pick<LightboxProps, "render" | "carousel" | "zoom" | "animation"> & {
5
+ export declare const ZoomContainer: React.FC<Pick<LightboxProps, "render" | "carousel" | "zoom" | "animation" | "on"> & {
6
6
  slide: Slide;
7
7
  offset: number;
8
8
  rect: ContainerRect;
@@ -1,8 +1,9 @@
1
1
  import * as React from "react";
2
- import { CLASS_FLEX_CENTER, CLASS_FULLSIZE, cleanup, clsx, cssClass, EVENT_ON_KEY_DOWN, EVENT_ON_POINTER_CANCEL, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_WHEEL, ImageSlide, isImageSlide, round, useContainerRect, useController, useEventCallback, useEvents, useLayoutEffect, useMotionPreference, } from "../../core/index.js";
2
+ import { CLASS_FLEX_CENTER, CLASS_FULLSIZE, cleanup, clsx, cssClass, EVENT_ON_KEY_DOWN, EVENT_ON_POINTER_CANCEL, EVENT_ON_POINTER_DOWN, EVENT_ON_POINTER_LEAVE, EVENT_ON_POINTER_MOVE, EVENT_ON_POINTER_UP, EVENT_ON_WHEEL, ImageSlide, isImageSlide, round, useContainerRect, useController, useEventCallback, useEvents, useLayoutEffect, useLightboxState, useMotionPreference, } from "../../core/index.js";
3
3
  import { useZoom } from "./ZoomContext.js";
4
4
  import { defaultZoomProps } from "./Zoom.js";
5
5
  import { ACTION_ZOOM_IN, ACTION_ZOOM_OUT } from "./index.js";
6
+ import { isResponsiveImageSlide, ResponsiveImage } from "./ResponsiveImage.js";
6
7
  const getSlideRects = (slide, cover, maxZoomPixelRatio, rect) => {
7
8
  var _a, _b;
8
9
  let slideRect = { width: 0, height: 0 };
@@ -35,12 +36,14 @@ const getSlideRects = (slide, cover, maxZoomPixelRatio, rect) => {
35
36
  return { slideRect, maxSlideRect };
36
37
  };
37
38
  const distance = (pointerA, pointerB) => ((pointerA.clientX - pointerB.clientX) ** 2 + (pointerA.clientY - pointerB.clientY) ** 2) ** 0.5;
38
- export const ZoomContainer = ({ slide, offset, rect, render, carousel, animation, zoom: originalZoomProps }) => {
39
+ export const ZoomContainer = ({ slide, offset, rect, render, carousel, animation, zoom: originalZoomProps, on }) => {
39
40
  var _a;
40
41
  const zoomProps = { ...defaultZoomProps, ...originalZoomProps };
42
+ const { state: { currentIndex }, } = useLightboxState();
41
43
  const [zoom, setZoom] = React.useState(1);
42
44
  const [offsetX, setOffsetX] = React.useState(0);
43
45
  const [offsetY, setOffsetY] = React.useState(0);
46
+ const [imageDimensions, setImageDimensions] = React.useState();
44
47
  const activePointers = React.useRef([]);
45
48
  const lastPointerDown = React.useRef(0);
46
49
  const zoomAnimation = React.useRef();
@@ -51,7 +54,7 @@ export const ZoomContainer = ({ slide, offset, rect, render, carousel, animation
51
54
  const { subscribeSensors, containerRef: controllerRef, containerRect: controllerRect } = useController();
52
55
  const { subscribe } = useEvents();
53
56
  const reduceMotion = useMotionPreference();
54
- const { slideRect, maxSlideRect: currentMaxSlideRect } = getSlideRects(slide, carousel.imageFit === "cover" || ("imageFit" in slide && slide.imageFit === "cover"), zoomProps.maxZoomPixelRatio, containerRect);
57
+ const { slideRect, maxSlideRect: currentMaxSlideRect } = getSlideRects({ ...slide, ...imageDimensions }, carousel.imageFit === "cover" || ("imageFit" in slide && slide.imageFit === "cover"), zoomProps.maxZoomPixelRatio, containerRect);
55
58
  const maxZoom = slideRect.width ? Math.max(round(currentMaxSlideRect.width / slideRect.width, 5), 1) : 1;
56
59
  const changeOffsets = useEventCallback((dx, dy, targetZoom) => {
57
60
  const newZoom = targetZoom || zoom;
@@ -295,7 +298,15 @@ export const ZoomContainer = ({ slide, offset, rect, render, carousel, animation
295
298
  : rect;
296
299
  let rendered = (_a = render.slide) === null || _a === void 0 ? void 0 : _a.call(render, slide, offset, scaledRect);
297
300
  if (!rendered && isImageSlide(slide)) {
298
- rendered = (React.createElement(ImageSlide, { slide: slide, offset: offset, rect: scaledRect, render: render, imageFit: carousel.imageFit }));
301
+ const slideProps = {
302
+ slide,
303
+ offset,
304
+ rect,
305
+ render,
306
+ imageFit: carousel.imageFit,
307
+ onClick: offset === 0 ? () => { var _a; return (_a = on.click) === null || _a === void 0 ? void 0 : _a.call(on, currentIndex); } : undefined,
308
+ };
309
+ rendered = isResponsiveImageSlide(slide) ? (React.createElement(ResponsiveImage, { ...slideProps, slide: slide })) : (React.createElement(ImageSlide, { onLoad: (img) => setImageDimensions({ width: img.naturalWidth, height: img.naturalHeight }), ...slideProps }));
299
310
  }
300
311
  return rendered ? (React.createElement("div", { ref: setContainerRef, className: clsx(cssClass(CLASS_FULLSIZE), cssClass(CLASS_FLEX_CENTER)), ...(offset === 0
301
312
  ? { style: { transform: `scale(${zoom}) translateX(${offsetX}px) translateY(${offsetY}px)` } }
@@ -5,7 +5,6 @@ declare type ZoomContextType = {
5
5
  isZoomSupported: boolean;
6
6
  setIsMinZoom: (value: boolean) => void;
7
7
  setIsMaxZoom: (value: boolean) => void;
8
- setIsZoomSupported: (value: boolean) => void;
9
8
  };
10
9
  export declare const useZoom: () => ZoomContextType;
11
10
  export declare const ZoomContextProvider: Component;
@@ -1,18 +1,20 @@
1
1
  import * as React from "react";
2
- import { makeUseContext } from "../../core/index.js";
2
+ import { isImageSlide, makeUseContext, useEventCallback, useLayoutEffect, useLightboxState } from "../../core/index.js";
3
3
  const ZoomContext = React.createContext(null);
4
4
  export const useZoom = makeUseContext("useZoom", "ZoomContext", ZoomContext);
5
- export const ZoomContextProvider = ({ children }) => {
5
+ export const ZoomContextProvider = ({ slides, children }) => {
6
6
  const [isMinZoom, setIsMinZoom] = React.useState(false);
7
7
  const [isMaxZoom, setIsMaxZoom] = React.useState(false);
8
8
  const [isZoomSupported, setIsZoomSupported] = React.useState(false);
9
+ const { state: { currentIndex }, } = useLightboxState();
10
+ const updateZoomSupported = useEventCallback(() => setIsZoomSupported(isImageSlide(slides[currentIndex])));
11
+ useLayoutEffect(updateZoomSupported, [currentIndex, updateZoomSupported]);
9
12
  const context = React.useMemo(() => ({
10
13
  isMinZoom,
11
14
  isMaxZoom,
12
15
  isZoomSupported,
13
16
  setIsMinZoom,
14
17
  setIsMaxZoom,
15
- setIsZoomSupported,
16
18
  }), [isMinZoom, isMaxZoom, isZoomSupported]);
17
19
  return React.createElement(ZoomContext.Provider, { value: context }, children);
18
20
  };
package/dist/styles.css CHANGED
@@ -57,8 +57,11 @@
57
57
  --yarl__direction: -1;
58
58
  }
59
59
  .yarl__slide_image {
60
+ max-width: 100%;
61
+ max-height: 100%;
60
62
  -o-object-fit: contain;
61
63
  object-fit: contain;
64
+ pointer-events: none;
62
65
  -moz-user-select: none;
63
66
  user-select: none;
64
67
  -webkit-user-select: none;
package/dist/types.d.ts CHANGED
@@ -2,6 +2,16 @@ import * as React from "react";
2
2
  import { ContainerRect } from "./core/hooks/useContainerRect.js";
3
3
  /** Image fit setting */
4
4
  export declare type ImageFit = "contain" | "cover";
5
+ /** Image source */
6
+ export interface ImageSource {
7
+ /** image URL */
8
+ src: string;
9
+ /** image width in pixels */
10
+ width: number;
11
+ /** image height in pixels */
12
+ height: number;
13
+ }
14
+ /** Generic slide */
5
15
  export interface GenericSlide {
6
16
  }
7
17
  /** Image slide properties */
@@ -19,14 +29,7 @@ export interface SlideImage extends GenericSlide {
19
29
  /** `object-fit` setting */
20
30
  imageFit?: ImageFit;
21
31
  /** alternative images to be passed to the 'srcSet' */
22
- srcSet?: {
23
- /** image URL */
24
- src: string;
25
- /** image width in pixels */
26
- width: number;
27
- /** image height in pixels */
28
- height: number;
29
- }[];
32
+ srcSet?: ImageSource[];
30
33
  }
31
34
  /** Supported slide types */
32
35
  export interface SlideTypes {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox",
3
- "version": "2.0.0-rc.3",
3
+ "version": "2.0.0-rc.4",
4
4
  "description": "Modern React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",
@@ -1,2 +0,0 @@
1
- import * as React from "react";
2
- export declare const useOffset: (onChange: (offset: number) => void) => [number, React.Dispatch<React.SetStateAction<number>>];
@@ -1,10 +0,0 @@
1
- import * as React from "react";
2
- import { useEventCallback } from "../../hooks/useEventCallback.js";
3
- export const useOffset = (onChange) => {
4
- const [offset, setOffset] = React.useState(0);
5
- const handleOnChange = useEventCallback(() => {
6
- onChange(offset);
7
- });
8
- React.useEffect(handleOnChange, [offset, handleOnChange]);
9
- return [offset, setOffset];
10
- };
@@ -1,3 +0,0 @@
1
- import { ZoomContainer } from "./ZoomContainer.js";
2
- /** Zoom slide wrapper */
3
- export declare const ZoomWrapper: typeof ZoomContainer;
@@ -1,26 +0,0 @@
1
- import * as React from "react";
2
- import { ImageSlide, isImageSlide, useEventCallback } from "../../core/index.js";
3
- import { ZoomContainer } from "./ZoomContainer.js";
4
- import { useZoom } from "./ZoomContext.js";
5
- export const ZoomWrapper = ({ slide, offset, rect, render, carousel, animation, zoom }) => {
6
- var _a;
7
- const { setIsZoomSupported, isZoomSupported } = useZoom();
8
- const zoomSupported = isImageSlide(slide) && (Boolean(slide.srcSet) || Boolean(slide.width && slide.height));
9
- const updateIsZoomSupported = useEventCallback(() => {
10
- if (offset === 0 && zoomSupported !== isZoomSupported) {
11
- setIsZoomSupported(zoomSupported);
12
- }
13
- });
14
- React.useEffect(updateIsZoomSupported, [offset, updateIsZoomSupported]);
15
- if (zoomSupported) {
16
- return (React.createElement(ZoomContainer, { slide: slide, offset: offset, rect: rect, render: render, carousel: carousel, animation: animation, zoom: zoom }));
17
- }
18
- const rendered = (_a = render.slide) === null || _a === void 0 ? void 0 : _a.call(render, slide, offset, rect);
19
- if (rendered) {
20
- return React.createElement(React.Fragment, null, rendered);
21
- }
22
- if (isImageSlide(slide)) {
23
- return React.createElement(ImageSlide, { slide: slide, offset: offset, rect: rect, render: render, imageFit: carousel.imageFit });
24
- }
25
- return null;
26
- };