yet-another-react-lightbox 1.10.0 → 1.12.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.
@@ -7,5 +7,6 @@ export declare type ImageSlideProps = {
7
7
  render?: Render;
8
8
  rect?: ContainerRect;
9
9
  imageFit?: ImageFit;
10
+ onClick?: () => void;
10
11
  };
11
- export declare const ImageSlide: ({ slide: image, offset, render, rect, imageFit }: ImageSlideProps) => JSX.Element;
12
+ export declare const ImageSlide: ({ slide: image, offset, render, rect, imageFit, onClick }: 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, imageFit }) => {
7
+ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit, onClick }) => {
8
8
  var _a, _b, _c, _d, _e, _f, _g;
9
9
  const [status, setStatus] = React.useState(SLIDE_STATUS_LOADING);
10
10
  const latestStatus = useLatest(status);
@@ -61,7 +61,7 @@ export const ImageSlide = ({ slide: image, offset, render, rect, imageFit }) =>
61
61
  ? `${Math.ceil((Math.min(estimateActualWidth(), rect.width) / window.innerWidth) * 100)}vw`
62
62
  : undefined;
63
63
  return (React.createElement(React.Fragment, null,
64
- React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, className: clsx(cssClass("slide_image"), cssClass("fullsize"), cover && cssClass("slide_image_cover"), status !== SLIDE_STATUS_COMPLETE && cssClass("slide_image_loading")), draggable: false, alt: image.alt, style: style, sizes: sizes, srcSet: srcSet, src: image.src }),
64
+ React.createElement("img", { ref: setImageRef, onLoad: onLoad, onError: onError, onClick: onClick, className: clsx(cssClass("slide_image"), cssClass("fullsize"), cover && cssClass("slide_image_cover"), status !== SLIDE_STATUS_COMPLETE && cssClass("slide_image_loading")), draggable: false, alt: image.alt, style: style, sizes: sizes, srcSet: srcSet, src: image.src }),
65
65
  status !== SLIDE_STATUS_COMPLETE && (React.createElement("div", { className: cssClass("slide_placeholder") },
66
66
  status === SLIDE_STATUS_LOADING &&
67
67
  ((render === null || render === void 0 ? void 0 : render.iconLoading) ? (render.iconLoading()) : (React.createElement(LoadingIcon, { className: clsx(cssClass("icon"), cssClass("slide_loading")) }))),
@@ -1,2 +1,3 @@
1
1
  import * as React from "react";
2
+ /** @deprecated migrate to useEventCallback */
2
3
  export declare const useLatest: <T>(value: T) => React.MutableRefObject<T>;
@@ -7,13 +7,18 @@ import { ImageSlide } from "../components/index.js";
7
7
  import { useController } from "./Controller.js";
8
8
  const CarouselSlide = ({ slide, offset }) => {
9
9
  const { setContainerRef, containerRect } = useContainerRect();
10
- const { latestProps } = useController();
10
+ const { latestProps, currentIndex } = useController();
11
11
  const { render } = latestProps.current;
12
12
  const renderSlide = (rect) => {
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, imageFit: latestProps.current.carousel.imageFit }));
16
+ rendered = (React.createElement(ImageSlide, { slide: slide, offset: offset, render: render, rect: rect, imageFit: latestProps.current.carousel.imageFit, onClick: latestProps.current.on.click && offset === 0
17
+ ? () => {
18
+ var _a, _b;
19
+ (_b = (_a = latestProps.current.on).click) === null || _b === void 0 ? void 0 : _b.call(_a, currentIndex);
20
+ }
21
+ : undefined }));
17
22
  }
18
23
  return rendered ? (React.createElement(React.Fragment, null, (_b = render.slideHeader) === null || _b === void 0 ? void 0 :
19
24
  _b.call(render, slide),
@@ -1,21 +1,21 @@
1
1
  import * as React from "react";
2
- import { cssClass, cssVar, isDefined, makeUseContext } from "../core/utils.js";
2
+ import { clsx, cssClass, cssVar, isDefined, makeUseContext } from "../core/utils.js";
3
3
  import { useEvents } from "../core/contexts/Events.js";
4
4
  import { createModule } from "../core/index.js";
5
5
  const defaultTextAlign = "start";
6
6
  const defaultMaxLines = 3;
7
- const cls = (className) => cssClass(`slide_${className}`);
7
+ const cssPrefix = (className) => cssClass(`slide_${className}`);
8
8
  const hasTitle = (slide) => "title" in slide ? typeof slide.title === "string" : false;
9
9
  const hasDescription = (slide) => "description" in slide ? typeof slide.description === "string" : false;
10
10
  const CaptionsContext = React.createContext(null);
11
11
  const useCaptions = makeUseContext("useCaptions", "CaptionsContext", CaptionsContext);
12
12
  const Title = ({ title }) => {
13
13
  const { toolbarWidth } = useCaptions();
14
- return (React.createElement("div", { className: cls(`title_container`) },
15
- React.createElement("div", { className: cls("title"), ...(toolbarWidth ? { style: { [cssVar("toolbar_width")]: `${toolbarWidth}px` } } : null) }, title)));
14
+ return (React.createElement("div", { className: clsx(cssPrefix("captions_container"), cssPrefix("title_container")) },
15
+ React.createElement("div", { className: cssPrefix("title"), ...(toolbarWidth ? { style: { [cssVar("toolbar_width")]: `${toolbarWidth}px` } } : null) }, title)));
16
16
  };
17
- const Description = ({ description, align, maxLines }) => (React.createElement("div", { className: cls("description_container") },
18
- React.createElement("div", { className: cls("description"), ...(align !== defaultTextAlign || maxLines !== defaultMaxLines
17
+ const Description = ({ description, align, maxLines }) => (React.createElement("div", { className: clsx(cssPrefix("captions_container"), cssPrefix("description_container")) },
18
+ React.createElement("div", { className: cssPrefix("description"), ...(align !== defaultTextAlign || maxLines !== defaultMaxLines
19
19
  ? {
20
20
  style: {
21
21
  [cssVar("slide_description_text_align")]: align,
@@ -39,9 +39,12 @@ export const VideoSlide = ({ slide, offset }) => {
39
39
  if (!width || !height || !containerRect)
40
40
  return null;
41
41
  const widthBound = width / height > containerRect.width / containerRect.height;
42
+ const elementWidth = widthBound ? containerRect.width : Math.round((containerRect.height / height) * width);
43
+ const elementHeight = !widthBound ? containerRect.height : Math.round((containerRect.width / width) * height);
42
44
  return {
43
- width: widthBound ? containerRect.width : Math.round((containerRect.height / height) * width),
44
- height: !widthBound ? containerRect.height : Math.round((containerRect.width / width) * height),
45
+ width: elementWidth,
46
+ height: elementHeight,
47
+ style: { width: elementWidth, height: elementHeight, maxWidth: "100%", maxHeight: "100%" },
45
48
  };
46
49
  };
47
50
  const resolveBoolean = (attr) => {
@@ -28,6 +28,8 @@ declare module "../types.js" {
28
28
  wheelZoomDistanceFactor?: number;
29
29
  /** pinch zoom distance factor */
30
30
  pinchZoomDistanceFactor?: number;
31
+ /** if `true`, enables image zoom via scroll gestures for mouse and trackpad users */
32
+ scrollToZoom?: boolean;
31
33
  };
32
34
  }
33
35
  interface AnimationSettings {
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { cleanup, clsx, createIcon, createModule, cssClass, IconButton, ImageSlide, label, makeUseContext, round, useContainerRect, useController, useLayoutEffect, useEvents, useMotionPreference, } from "../core/index.js";
2
+ import { cleanup, clsx, createIcon, createModule, cssClass, IconButton, ImageSlide, label, makeUseContext, round, useContainerRect, useController, useEvents, useLayoutEffect, useMotionPreference, } from "../core/index.js";
3
3
  const defaultZoomProps = {
4
4
  maxZoomPixelRatio: 1,
5
5
  zoomInMultiplier: 2,
@@ -9,6 +9,7 @@ const defaultZoomProps = {
9
9
  keyboardMoveDistance: 50,
10
10
  wheelZoomDistanceFactor: 100,
11
11
  pinchZoomDistanceFactor: 100,
12
+ scrollToZoom: false,
12
13
  };
13
14
  const ZoomInIcon = createIcon("ZoomIn", React.createElement(React.Fragment, null,
14
15
  React.createElement("path", { d: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" }),
@@ -172,11 +173,29 @@ const ZoomContainer = ({ slide, offset, rect, render, carousel, animation, zoom:
172
173
  offsetY: Math.min(Math.abs(newOffsetY), Math.max(maxOffsetY, 0)) * Math.sign(newOffsetY),
173
174
  }));
174
175
  }, []);
176
+ const changeZoom = React.useCallback((value, rapid, dx, dy) => {
177
+ const { current } = refs;
178
+ const { state: { zoom }, containerRef, containerRect, maxZoom, } = current;
179
+ if (!containerRef.current || !containerRect)
180
+ return;
181
+ const newZoom = round(Math.min(Math.max(value + 0.001 < maxZoom ? value : maxZoom, 1), maxZoom), 5);
182
+ if (newZoom === zoom)
183
+ return;
184
+ if (!rapid) {
185
+ current.zoomAnimationStart = window.getComputedStyle(containerRef.current).transform;
186
+ }
187
+ changeOffsets(dx ? dx * (1 / zoom - 1 / newZoom) : 0, dy ? dy * (1 / zoom - 1 / newZoom) : 0, newZoom);
188
+ setState((prev) => ({ ...prev, zoom: newZoom }));
189
+ }, [changeOffsets]);
175
190
  useLayoutEffect(() => {
176
191
  if (refs.current.state.zoom > 1) {
192
+ const { maxZoom, state: { zoom: currentZoom }, } = refs.current;
193
+ if (currentZoom > maxZoom) {
194
+ changeZoom(maxZoom, true);
195
+ }
177
196
  changeOffsets();
178
197
  }
179
- }, [currentControllerRect.width, currentControllerRect.height, changeOffsets]);
198
+ }, [currentControllerRect.width, currentControllerRect.height, changeOffsets, changeZoom]);
180
199
  useLayoutEffect(() => {
181
200
  var _a, _b;
182
201
  const { current } = refs;
@@ -226,20 +245,6 @@ const ZoomContainer = ({ slide, offset, rect, render, carousel, animation, zoom:
226
245
  }
227
246
  }
228
247
  }, [offset, state.zoom, currentMaxZoom, isMinZoom, isMaxZoom, setIsMinZoom, setIsMaxZoom]);
229
- const changeZoom = React.useCallback((value, rapid, dx, dy) => {
230
- const { current } = refs;
231
- const { state: { zoom }, containerRef, containerRect, maxZoom, } = current;
232
- if (!containerRef.current || !containerRect)
233
- return;
234
- const newZoom = round(Math.min(Math.max(value + 0.001 < maxZoom ? value : maxZoom, 1), maxZoom), 5);
235
- if (newZoom === zoom)
236
- return;
237
- if (!rapid) {
238
- current.zoomAnimationStart = window.getComputedStyle(containerRef.current).transform;
239
- }
240
- changeOffsets(dx ? dx * (1 / zoom - 1 / newZoom) : 0, dy ? dy * (1 / zoom - 1 / newZoom) : 0, newZoom);
241
- setState((prev) => ({ ...prev, zoom: newZoom }));
242
- }, [changeOffsets]);
243
248
  const translateCoordinates = React.useCallback((event) => {
244
249
  const { controllerRef } = refs.current;
245
250
  if (controllerRef.current) {
@@ -252,9 +257,13 @@ const ZoomContainer = ({ slide, offset, rect, render, carousel, animation, zoom:
252
257
  }, []);
253
258
  const onKeyDown = React.useCallback((event) => {
254
259
  const { state: { zoom }, zoomProps: { keyboardMoveDistance, zoomInMultiplier }, } = refs.current;
260
+ const preventDefault = () => {
261
+ event.preventDefault();
262
+ event.stopPropagation();
263
+ };
255
264
  if (zoom > 1) {
256
265
  const move = (deltaX, deltaY) => {
257
- event.stopPropagation();
266
+ preventDefault();
258
267
  changeOffsets(deltaX, deltaY);
259
268
  };
260
269
  if (event.key === "ArrowDown") {
@@ -270,28 +279,35 @@ const ZoomContainer = ({ slide, offset, rect, render, carousel, animation, zoom:
270
279
  move(keyboardMoveDistance, 0);
271
280
  }
272
281
  }
273
- if (event.key === "+" || (event.key === "=" && event.metaKey)) {
274
- changeZoom(zoom * zoomInMultiplier);
282
+ const handleChangeZoom = (zoomValue) => {
283
+ preventDefault();
284
+ changeZoom(zoomValue);
285
+ };
286
+ const hasMeta = () => event.getModifierState("Meta") || event.getModifierState("OS");
287
+ if (event.key === "+" || (event.key === "=" && hasMeta())) {
288
+ handleChangeZoom(zoom * zoomInMultiplier);
275
289
  }
276
- else if (event.key === "-" || (event.key === "_" && event.metaKey)) {
277
- changeZoom(zoom / zoomInMultiplier);
290
+ else if (event.key === "-" || (event.key === "_" && hasMeta())) {
291
+ handleChangeZoom(zoom / zoomInMultiplier);
278
292
  }
279
- else if (event.key === "0" && event.metaKey) {
280
- changeZoom(1);
293
+ else if (event.key === "0" && hasMeta()) {
294
+ handleChangeZoom(1);
281
295
  }
282
296
  }, [changeZoom, changeOffsets]);
283
297
  const onWheel = React.useCallback((event) => {
284
- const { state: { zoom }, zoomProps: { wheelZoomDistanceFactor }, } = refs.current;
285
- if (event.ctrlKey) {
286
- event.stopPropagation();
298
+ const { state: { zoom }, zoomProps: { wheelZoomDistanceFactor, scrollToZoom }, } = refs.current;
299
+ if (event.ctrlKey || scrollToZoom) {
287
300
  if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
301
+ event.stopPropagation();
288
302
  changeZoom(zoom * (1 - event.deltaY / wheelZoomDistanceFactor), true, ...translateCoordinates(event));
303
+ return;
289
304
  }
290
- return;
291
305
  }
292
306
  if (zoom > 1) {
293
307
  event.stopPropagation();
294
- changeOffsets(event.deltaX, event.deltaY);
308
+ if (!scrollToZoom) {
309
+ changeOffsets(event.deltaX, event.deltaY);
310
+ }
295
311
  }
296
312
  }, [changeZoom, changeOffsets, translateCoordinates]);
297
313
  const clearPointer = React.useCallback((event) => {
@@ -1,27 +1,24 @@
1
+ .yarl__slide_captions_container {
2
+ position: absolute;
3
+ left: var(--yarl__slide_captions_container_left, 0);
4
+ right: var(--yarl__slide_captions_container_right, 0);
5
+ padding: var(--yarl__slide_captions_container_padding, 16px);
6
+ background: var(--yarl__slide_captions_container_background, rgba(0, 0, 0, 0.5));
7
+ -webkit-transform: translateZ(0);
8
+ transform: translateZ(0);
9
+ }
1
10
  .yarl__slide_title {
2
- font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
3
- font-size: 1.25rem;
4
- font-weight: 700;
11
+ font-size: var(--yarl__slide_title_font_size, 125%);
12
+ font-weight: var(--yarl__slide_title_font_weight, bolder);
5
13
  max-width: calc(100% - var(--yarl__toolbar_width, 0px));
6
14
  overflow: hidden;
7
15
  text-overflow: ellipsis;
8
16
  white-space: nowrap;
9
17
  }
10
18
  .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
- -webkit-transform: translateZ(0);
18
- transform: translateZ(0);
19
+ top: var(--yarl__slide_title_container_top, 0);
19
20
  }
20
21
  .yarl__slide_description {
21
- font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
22
- font-size: 1rem;
23
- line-height: 1.2;
24
- font-weight: 500;
25
22
  overflow: hidden;
26
23
  -webkit-hyphens: auto;
27
24
  hyphens: auto;
@@ -31,12 +28,5 @@
31
28
  text-align: var(--yarl__slide_description_text_align, start);
32
29
  }
33
30
  .yarl__slide_description_container {
34
- position: absolute;
35
- left: 0;
36
- right: 0;
37
- bottom: 0;
38
- padding: 16px;
39
- background: rgba(0, 0, 0, 0.5);
40
- -webkit-transform: translateZ(0);
41
- transform: translateZ(0);
31
+ bottom: var(--yarl__slide_description_container_bottom, 0);
42
32
  }
package/dist/types.d.ts CHANGED
@@ -99,6 +99,8 @@ export interface Render {
99
99
  export interface Callbacks {
100
100
  /** a callback called when a slide becomes active */
101
101
  view?: (index: number) => void;
102
+ /** a callback called when a slide is clicked */
103
+ click?: (index: number) => void;
102
104
  /** a callback called when the portal starts opening */
103
105
  entering?: () => void;
104
106
  /** a callback called when the portal opens */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Modern React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",
@@ -96,19 +96,19 @@
96
96
  "@semantic-release/github": "^8.0.5",
97
97
  "@testing-library/jest-dom": "^5.16.4",
98
98
  "@testing-library/react": "^13.3.0",
99
- "@testing-library/user-event": "^14.2.3",
100
- "@types/jest": "^28.1.5",
99
+ "@testing-library/user-event": "^14.3.0",
100
+ "@types/jest": "^28.1.6",
101
101
  "@types/react": "^18.0.15",
102
102
  "@types/react-dom": "^18.0.6",
103
- "@typescript-eslint/eslint-plugin": "^5.30.6",
104
- "@typescript-eslint/parser": "^5.30.6",
105
- "autoprefixer": "^10.4.7",
106
- "eslint": "^8.19.0",
103
+ "@typescript-eslint/eslint-plugin": "^5.31.0",
104
+ "@typescript-eslint/parser": "^5.31.0",
105
+ "autoprefixer": "^10.4.8",
106
+ "eslint": "^8.20.0",
107
107
  "eslint-config-airbnb": "^19.0.4",
108
108
  "eslint-config-airbnb-typescript": "^17.0.0",
109
109
  "eslint-config-prettier": "^8.5.0",
110
110
  "eslint-plugin-import": "^2.26.0",
111
- "eslint-plugin-jsx-a11y": "^6.6.0",
111
+ "eslint-plugin-jsx-a11y": "^6.6.1",
112
112
  "eslint-plugin-prettier": "^4.2.1",
113
113
  "eslint-plugin-react": "^7.30.1",
114
114
  "eslint-plugin-react-hooks": "^4.6.0",
@@ -123,8 +123,8 @@
123
123
  "react": "^18.2.0",
124
124
  "react-dom": "^18.2.0",
125
125
  "rimraf": "^3.0.2",
126
- "sass": "^1.53.0",
127
- "ts-jest": "^28.0.6",
126
+ "sass": "^1.54.0",
127
+ "ts-jest": "^28.0.7",
128
128
  "typescript": "^4.7.4"
129
129
  },
130
130
  "keywords": [