yet-another-react-lightbox-lite 1.10.0 → 1.11.1

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lightweight React lightbox component. This is a trimmed-down version of the
4
4
  [yet-another-react-lightbox](https://github.com/igordanchenko/yet-another-react-lightbox)
5
- that provides essential lightbox features and slick UX with just 4.6KB bundle
5
+ that provides essential lightbox features and slick UX with around 5KB bundle
6
6
  size.
7
7
 
8
8
  ## Overview
@@ -12,7 +12,7 @@ size.
12
12
  [![License MIT](https://img.shields.io/npm/l/yet-another-react-lightbox-lite.svg?color=blue)](https://github.com/igordanchenko/yet-another-react-lightbox-lite/blob/main/LICENSE)
13
13
 
14
14
  - **Built for React:** works with React 18+
15
- - **UX:** supports keyboard, mouse, touchpad and touchscreen navigation
15
+ - **UX:** supports keyboard, mouse, touchpad, and touchscreen navigation
16
16
  - **Zoom:** zoom is supported out of the box
17
17
  - **Performance:** preloads a fixed number of images without compromising
18
18
  performance or UX
@@ -20,7 +20,7 @@ size.
20
20
  supported out of the box
21
21
  - **Customization:** customize any UI element or add your own custom slides
22
22
  - **No bloat:** supports only essential lightbox features
23
- - **TypeScript:** type definitions come built-in in the package
23
+ - **TypeScript:** type definitions come built-in with the package
24
24
 
25
25
  ![Yet Another React Lightbox Lite | Example](https://images.yet-another-react-lightbox.com/example-lite.jpg)
26
26
 
@@ -91,6 +91,8 @@ To utilize responsive images with automatic resolution switching, provide
91
91
  slides={[
92
92
  {
93
93
  src: "/image1x3840.jpg",
94
+ width: 3840,
95
+ height: 2560,
94
96
  srcSet: [
95
97
  { src: "/image1x320.jpg", width: 320, height: 213 },
96
98
  { src: "/image1x640.jpg", width: 640, height: 427 },
@@ -112,7 +114,15 @@ advantage of the
112
114
  [next/image](https://nextjs.org/docs/pages/api-reference/components/image)
113
115
  component. The `next/image` component provides a more efficient way to handle
114
116
  images in your Next.js project. You can replace the standard `<img>` element
115
- with `next/image` with the following `render.slide` render function.
117
+ with `next/image` using the following `render.slide` render function.
118
+
119
+ ```tsx
120
+ declare module "yet-another-react-lightbox-lite" {
121
+ interface SlideImage {
122
+ blurDataURL?: string;
123
+ }
124
+ }
125
+ ```
116
126
 
117
127
  ```tsx
118
128
  <Lightbox
@@ -140,7 +150,7 @@ with `next/image` with the following `render.slide` render function.
140
150
  height={height}
141
151
  loading="eager"
142
152
  draggable={false}
143
- blurDataURL={(slide as any).blurDataURL}
153
+ blurDataURL={slide.blurDataURL}
144
154
  style={{
145
155
  minWidth: 0,
146
156
  minHeight: 0,
@@ -158,7 +168,7 @@ with `next/image` with the following `render.slide` render function.
158
168
 
159
169
  ## API
160
170
 
161
- Yet Another React Lightbox Lite comes with CSS stylesheet that needs to be
171
+ Yet Another React Lightbox Lite comes with a CSS stylesheet that needs to be
162
172
  imported in your app.
163
173
 
164
174
  ```tsx
@@ -179,6 +189,10 @@ Image slide props:
179
189
 
180
190
  - `src` - image source (required)
181
191
  - `alt` - image `alt` attribute
192
+ - `width` - image width in pixels
193
+ - `height` - image height in pixels
194
+ - `srcSet` - alternative images for responsive resolution switching (see
195
+ [Responsive Images](#responsive-images))
182
196
 
183
197
  ### index
184
198
 
@@ -194,7 +208,7 @@ A callback to update current slide index state. This prop is required.
194
208
 
195
209
  ### labels
196
210
 
197
- Type: `keyof Labels`
211
+ Type: `object`
198
212
 
199
213
  Custom UI labels / translations.
200
214
 
@@ -215,7 +229,8 @@ Type: `object`
215
229
 
216
230
  Toolbar settings.
217
231
 
218
- - `buttons` - custom toolbar buttons (type: `ReactNode[]`)
232
+ - `buttons` - custom toolbar buttons (type: `ReactNode[]`). Each button should
233
+ have a unique `key` attribute.
219
234
  - `fixed` - if `true`, the toolbar is positioned statically above the carousel
220
235
 
221
236
  Usage example:
@@ -226,13 +241,14 @@ Usage example:
226
241
  fixed: true,
227
242
  buttons: [
228
243
  <button
244
+ key="custom-button"
229
245
  type="button"
230
246
  className="yarll__button"
231
247
  onClick={() => {
232
248
  // ...
233
249
  }}
234
250
  >
235
- <ButtonIcon />
251
+ Download
236
252
  </button>,
237
253
  ],
238
254
  }}
@@ -247,7 +263,8 @@ Type: `object`
247
263
  Carousel settings.
248
264
 
249
265
  - `preload` - the lightbox preloads `(2 * preload + 1)` slides (default: `2`)
250
- - `imageProps` - custom image slide attributes
266
+ - `imageProps` - custom image slide attributes (an object or a function
267
+ receiving the current slide and returning an object)
251
268
 
252
269
  Usage example:
253
270
 
@@ -261,6 +278,17 @@ Usage example:
261
278
  />
262
279
  ```
263
280
 
281
+ You can also use a function to provide per-slide attributes:
282
+
283
+ ```tsx
284
+ <Lightbox
285
+ carousel={{
286
+ imageProps: (slide) => ({ "data-alt": slide.alt }),
287
+ }}
288
+ // ...
289
+ />
290
+ ```
291
+
264
292
  ### controller
265
293
 
266
294
  Type: `object`
@@ -337,7 +365,7 @@ rendered right under the slide. Alternatively, you can use
337
365
  `position: "absolute"` to position the extra elements relative to the slide.
338
366
 
339
367
  For example, you can use the `slideFooter` render function to add slides
340
- descriptions.
368
+ descriptions (see [Custom Slide Attributes](#custom-slide-attributes)).
341
369
 
342
370
  ```tsx
343
371
  <Lightbox
@@ -355,11 +383,24 @@ descriptions.
355
383
  Render custom controls or additional elements in the lightbox (use absolute
356
384
  positioning).
357
385
 
358
- For example, you can use the `render.controls` render function to implement
386
+ For example, you can use the `render.controls` render function to implement a
359
387
  slides counter.
360
388
 
361
389
  ```tsx
390
+ const slides = [
391
+ { src: "/image1.jpg" },
392
+ { src: "/image2.jpg" },
393
+ { src: "/image3.jpg" },
394
+ ];
395
+
396
+ const [index, setIndex] = useState<number>();
397
+
398
+ // ...
399
+
362
400
  <Lightbox
401
+ slides={slides}
402
+ index={index}
403
+ setIndex={setIndex}
363
404
  render={{
364
405
  controls: () =>
365
406
  index !== undefined && (
@@ -368,8 +409,7 @@ slides counter.
368
409
  </div>
369
410
  ),
370
411
  }}
371
- // ...
372
- />
412
+ />;
373
413
  ```
374
414
 
375
415
  #### iconPrev: () => ReactNode
@@ -386,7 +426,7 @@ Render custom `Close` icon.
386
426
 
387
427
  ### styles
388
428
 
389
- Type: `{ [key in Slot]?: SlotCSSProperties }`
429
+ Type: `object`
390
430
 
391
431
  Customization slots styles allow you to specify custom CSS styles or override
392
432
  `--yarll__*` CSS variables by passing your custom styles through to the
@@ -441,6 +481,8 @@ Usage example:
441
481
  ## Custom Slide Attributes
442
482
 
443
483
  You can add custom slide attributes with the following module augmentation.
484
+ Augmenting `GenericSlide` extends all slide types, including custom ones. To
485
+ extend only image slides, augment `SlideImage` instead.
444
486
 
445
487
  ```tsx
446
488
  declare module "yet-another-react-lightbox-lite" {
@@ -450,12 +492,20 @@ declare module "yet-another-react-lightbox-lite" {
450
492
  }
451
493
  ```
452
494
 
495
+ ```tsx
496
+ declare module "yet-another-react-lightbox-lite" {
497
+ interface SlideImage {
498
+ blurDataURL?: string;
499
+ }
500
+ }
501
+ ```
502
+
453
503
  ## Custom Slides
454
504
 
455
505
  You can add custom slide types through module augmentation and render them with
456
- the `render.slide` render function.
506
+ the `render.slide` function.
457
507
 
458
- Here is an example demonstrating video slides implementation.
508
+ Here is an example demonstrating video slide support.
459
509
 
460
510
  ```tsx
461
511
  declare module "yet-another-react-lightbox-lite" {
@@ -565,16 +615,16 @@ layout shift of some fixed-positioned page elements when the lightbox opens. To
565
615
  address this, you can assign the `yarll__fixed` CSS class to your
566
616
  fixed-positioned elements to keep them in place. Please note that the
567
617
  fixed-positioned element container should not have its own border or padding
568
- styles. If that's the case, you can always add an extra wrapper that just
569
- defines the fixed position without visual styles.
618
+ styles. If it does, you can always add an extra wrapper that just defines the
619
+ fixed position without visual styles.
570
620
 
571
621
  ## Text Selection
572
622
 
573
623
  The lightbox is rendered with the `user-select: none` CSS style. If you'd like
574
624
  to make some of your custom elements user-selectable, use the
575
625
  `yarll__selectable` CSS class. This class sets the `user-select: text` style and
576
- turns off click-and-drag slide navigation, likely interfering with text
577
- selection UX.
626
+ turns off click-and-drag slide navigation, which would likely interfere with
627
+ text selection UX.
578
628
 
579
629
  ## Hooks
580
630
 
@@ -600,6 +650,42 @@ The hook provides an object with the following props:
600
650
  - `changeZoom` - change zoom level
601
651
  - `changeOffsets` - change position offsets
602
652
 
653
+ Usage example:
654
+
655
+ ```tsx
656
+ function ZoomControls() {
657
+ const { zoom, maxZoom, changeZoom } = useZoom();
658
+
659
+ return (
660
+ <div style={{ position: "absolute", bottom: 16, left: 16 }}>
661
+ <button
662
+ type="button"
663
+ disabled={zoom >= maxZoom}
664
+ onClick={() => changeZoom(zoom * 2)}
665
+ >
666
+ Zoom In
667
+ </button>
668
+ <button
669
+ type="button"
670
+ disabled={zoom <= 1}
671
+ onClick={() => changeZoom(zoom / 2)}
672
+ >
673
+ Zoom Out
674
+ </button>
675
+ </div>
676
+ );
677
+ }
678
+ ```
679
+
680
+ ```tsx
681
+ <Lightbox
682
+ render={{
683
+ controls: () => <ZoomControls />,
684
+ }}
685
+ // ...
686
+ />
687
+ ```
688
+
603
689
  ## License
604
690
 
605
691
  MIT © 2024 [Igor Danchenko](https://github.com/igordanchenko)
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { Key, Dispatch, SetStateAction, ReactNode, ComponentProps, CSSProperties, MouseEvent } from 'react';
1
+ import { ComponentProps, Key, Dispatch, SetStateAction, ReactNode, CSSProperties, MouseEvent } from 'react';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
 
4
4
  /** Lightbox props */
5
5
  interface LightboxProps {
6
6
  /** slides to display in the lightbox */
7
- slides: Slide[];
7
+ slides: readonly Slide[];
8
8
  /** slide index */
9
9
  index: number | undefined;
10
10
  /** slide index change callback */
@@ -55,7 +55,7 @@ interface SlideImage extends GenericSlide {
55
55
  /** image 'alt' attribute */
56
56
  alt?: string;
57
57
  /** alternative images to be passed to the 'srcSet' */
58
- srcSet?: ImageSource[];
58
+ srcSet?: readonly ImageSource[];
59
59
  }
60
60
  /** Image source */
61
61
  interface ImageSource {
@@ -126,7 +126,7 @@ interface RenderSlideProps {
126
126
  /** Toolbar settings */
127
127
  interface ToolbarSettings {
128
128
  /** custom toolbar buttons */
129
- buttons?: ReactNode[];
129
+ buttons?: readonly ReactNode[];
130
130
  /** if `true`, the toolbar is positioned statically above the carousel */
131
131
  fixed?: boolean;
132
132
  }
@@ -135,7 +135,7 @@ interface CarouselSettings {
135
135
  /** the lightbox preloads (2 * preload + 1) slides */
136
136
  preload?: number;
137
137
  /** custom image slide attributes */
138
- imageProps?: ComponentProps<"img">;
138
+ imageProps?: ComponentProps<"img"> | ((slide: SlideImage) => ComponentProps<"img">);
139
139
  }
140
140
  /** Controller settings */
141
141
  interface ControllerSettings {
@@ -151,7 +151,7 @@ interface ZoomSettings {
151
151
  /** disable zoom on image slides */
152
152
  disabled?: boolean;
153
153
  /** zoom-enabled custom slide types */
154
- supports?: SlideTypeKey[];
154
+ supports?: readonly SlideTypeKey[];
155
155
  }
156
156
  /** Customization slots */
157
157
  interface SlotType {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useContext, createContext, useState, useEffect, useRef, useLayoutEffect, useCallback, useMemo, cloneElement, isValidElement } from 'react';
2
+ import { useContext, createContext, useState, useEffect, useRef, useLayoutEffect, useCallback, useMemo } from 'react';
3
3
  import { flushSync, createPortal } from 'react-dom';
4
4
 
5
5
  const cssPrefix = "yarll__";
@@ -10,7 +10,7 @@ function cssVar(name) {
10
10
  return `--${cssPrefix}${name}`;
11
11
  }
12
12
  function clsx(...classes) {
13
- return [...classes].filter(Boolean).join(" ");
13
+ return classes.filter(Boolean).join(" ");
14
14
  }
15
15
  function transition(callback) {
16
16
  if (document.startViewTransition) {
@@ -49,7 +49,7 @@ function isImageSlide(slide) {
49
49
  return (slide.type === undefined || slide.type === "image") && typeof slide.src === "string";
50
50
  }
51
51
  function getChildren(element) {
52
- return element?.children || [];
52
+ return Array.from(element?.children || []);
53
53
  }
54
54
 
55
55
  const LightboxContext = createContext(null);
@@ -71,7 +71,8 @@ function getImageDimensions(slide, rect) {
71
71
  }
72
72
  function ImageSlide({ slide, rect, zoom }) {
73
73
  const [scale, setScale] = useState(1);
74
- const { carousel: { imageProps } = {}, styles } = useLightboxContext();
74
+ const { carousel: { imageProps: imagePropsParam } = {}, styles } = useLightboxContext();
75
+ const imageProps = typeof imagePropsParam === "function" ? imagePropsParam(slide) : imagePropsParam;
75
76
  useEffect(() => {
76
77
  if (zoom <= scale)
77
78
  return;
@@ -83,7 +84,8 @@ function ImageSlide({ slide, rect, zoom }) {
83
84
  };
84
85
  }, [zoom, scale]);
85
86
  const srcSet = slide.srcSet
86
- ?.sort((a, b) => a.width - b.width)
87
+ ?.slice()
88
+ .sort((a, b) => a.width - b.width)
87
89
  .map((image) => `${image.src} ${image.width}w`)
88
90
  .join(", ");
89
91
  const [width, height] = getImageDimensions(slide, rect);
@@ -91,6 +93,14 @@ function ImageSlide({ slide, rect, zoom }) {
91
93
  return (jsx("img", { draggable: false, style: styles?.image, className: cssClass("slide_image"), srcSet: srcSet, sizes: sizes, width: width, height: height, src: slide.src, alt: slide.alt ?? "", ...imageProps }));
92
94
  }
93
95
 
96
+ function useEventCallback(fn) {
97
+ const ref = useRef(fn);
98
+ useLayoutEffect(() => {
99
+ ref.current = fn;
100
+ });
101
+ return useCallback((...args) => ref.current(...args), []);
102
+ }
103
+
94
104
  const ZoomContext = createContext(null);
95
105
  const useZoom = makeUseContext(ZoomContext);
96
106
  const ZoomInternalContext = createContext(null);
@@ -102,6 +112,8 @@ function Zoom({ children }) {
102
112
  const [rect, setRect] = useState();
103
113
  const observer = useRef(undefined);
104
114
  const carouselRef = useRef(null);
115
+ const carouselRectRef = useRef(undefined);
116
+ const slideDimensionsRef = useRef([0, 0]);
105
117
  const { index, slides, zoom: { supports, disabled } = {} } = useLightboxContext();
106
118
  const [prevIndex, setPrevIndex] = useState(index);
107
119
  if (index !== prevIndex) {
@@ -111,53 +123,61 @@ function Zoom({ children }) {
111
123
  setPrevIndex(index);
112
124
  }
113
125
  const slide = slides[index];
114
- const maxZoom = (isImageSlide(slide) && !disabled) || (supports || []).includes(slide.type)
126
+ const maxZoom = slide && ((isImageSlide(slide) && !disabled) || (slide.type !== undefined && supports?.includes(slide.type)))
115
127
  ? 8
116
128
  : 1;
129
+ const carouselHalfWidth = (rect?.width || 0) / 2;
130
+ const carouselHalfHeight = (rect?.height || 0) / 2;
131
+ const clampOffsets = useEventCallback((x = offsetX, y = offsetY, currentZoom = zoom) => {
132
+ const [slideHalfWidth, slideHalfHeight] = slideDimensionsRef.current;
133
+ const maxOffsetX = Math.max(slideHalfWidth * currentZoom - carouselHalfWidth, 0);
134
+ const maxOffsetY = Math.max(slideHalfHeight * currentZoom - carouselHalfHeight, 0);
135
+ setOffsetX(Math.min(maxOffsetX, Math.max(-maxOffsetX, x)));
136
+ setOffsetY(Math.min(maxOffsetY, Math.max(-maxOffsetY, y)));
137
+ });
117
138
  useLayoutEffect(() => {
118
- const carouselHalfWidth = (rect?.width || 0) / 2;
119
- const carouselHalfHeight = (rect?.height || 0) / 2;
120
- const [slideHalfWidth, slideHalfHeight] = Array.from(getChildren(Array.from(getChildren(carouselRef.current)).find((node) => node instanceof HTMLElement && !node.hidden)))
139
+ slideDimensionsRef.current = getChildren(getChildren(carouselRef.current).find((node) => node instanceof HTMLElement && !node.hidden))
121
140
  .filter((node) => node instanceof HTMLElement)
122
141
  .map((node) => [
123
142
  Math.max(carouselHalfWidth - node.offsetLeft, node.offsetLeft + node.offsetWidth - carouselHalfWidth),
124
143
  Math.max(carouselHalfHeight - node.offsetTop, node.offsetTop + node.offsetHeight - carouselHalfHeight),
125
144
  ])
126
145
  .reduce(([maxWidth, maxHeight], [width, height]) => [Math.max(width, maxWidth), Math.max(height, maxHeight)], [0, 0]);
127
- const maxOffsetX = Math.max(slideHalfWidth * zoom - carouselHalfWidth, 0);
128
- const maxOffsetY = Math.max(slideHalfHeight * zoom - carouselHalfHeight, 0);
129
- setOffsetX(Math.min(maxOffsetX, Math.max(-maxOffsetX, offsetX)));
130
- setOffsetY(Math.min(maxOffsetY, Math.max(-maxOffsetY, offsetY)));
131
- }, [zoom, rect, offsetX, offsetY]);
146
+ clampOffsets();
147
+ }, [carouselHalfWidth, carouselHalfHeight, index, clampOffsets]);
132
148
  const setCarouselRef = useCallback((node) => {
133
149
  carouselRef.current = node;
134
150
  observer.current?.disconnect();
135
151
  observer.current = undefined;
136
- const updateRect = () => setRect(node ? { width: node.clientWidth, height: node.clientHeight } : undefined);
152
+ const updateRect = () => {
153
+ carouselRectRef.current = node?.getBoundingClientRect();
154
+ setRect(node ? { width: node.clientWidth, height: node.clientHeight } : undefined);
155
+ };
156
+ updateRect();
137
157
  if (node && typeof ResizeObserver !== "undefined") {
138
158
  observer.current = new ResizeObserver(updateRect);
139
159
  observer.current.observe(node);
140
160
  }
141
- else {
142
- updateRect();
143
- }
144
161
  }, []);
145
- const changeOffsets = useCallback((dx, dy) => {
146
- setOffsetX(offsetX + dx);
147
- setOffsetY(offsetY + dy);
148
- }, [offsetX, offsetY]);
149
- const changeZoom = useCallback((targetZoom, event) => {
162
+ const changeOffsets = useEventCallback((dx, dy) => {
163
+ clampOffsets(offsetX + dx, offsetY + dy, zoom);
164
+ });
165
+ const changeZoom = useEventCallback((targetZoom, event) => {
150
166
  const newZoom = Math.min(Math.max(targetZoom, 1), maxZoom);
151
167
  setZoom(newZoom);
152
- if (event && carouselRef.current) {
168
+ let newOffsetX = offsetX;
169
+ let newOffsetY = offsetY;
170
+ if (event && carouselRectRef.current) {
153
171
  const { clientX, clientY } = event;
154
- const { left, top, width, height } = carouselRef.current.getBoundingClientRect();
172
+ const { left, top, width, height } = carouselRectRef.current;
155
173
  const zoomDelta = newZoom / zoom - 1;
156
- changeOffsets((left + width / 2 + offsetX - clientX) * zoomDelta, (top + height / 2 + offsetY - clientY) * zoomDelta);
174
+ newOffsetX += (left + width / 2 + offsetX - clientX) * zoomDelta;
175
+ newOffsetY += (top + height / 2 + offsetY - clientY) * zoomDelta;
157
176
  }
158
- }, [zoom, maxZoom, offsetX, offsetY, changeOffsets]);
177
+ clampOffsets(newOffsetX, newOffsetY, newZoom);
178
+ });
159
179
  const context = useMemo(() => ({ rect, zoom, maxZoom, offsetX, offsetY, changeZoom, changeOffsets }), [rect, zoom, maxZoom, offsetX, offsetY, changeZoom, changeOffsets]);
160
- const internalContext = useMemo(() => ({ carouselRef, setCarouselRef }), [setCarouselRef]);
180
+ const internalContext = useMemo(() => ({ setCarouselRef }), [setCarouselRef]);
161
181
  return (jsx(ZoomContext.Provider, { value: context, children: jsx(ZoomInternalContext.Provider, { value: internalContext, children: children }) }));
162
182
  }
163
183
 
@@ -186,10 +206,10 @@ function Carousel() {
186
206
  return (jsx("div", { ref: setCarouselRef, style: styles?.carousel, className: cssClass("carousel"), role: "region", "aria-live": "polite", "aria-label": translateLabel(labels, "Photo gallery"), "aria-roledescription": translateLabel(labels, "Carousel"), children: rect &&
187
207
  Array.from({ length: 2 * preload + 1 }).map((_, i) => {
188
208
  const slideIndex = index - preload + i;
189
- if (slideIndex < 0 || slideIndex >= slides.length)
190
- return null;
191
209
  const slide = slides[slideIndex];
192
- return (jsx(CarouselSlide, { rect: rect, slide: slide, slideIndex: slideIndex, current: slideIndex === index }, slide.key ?? [`${slideIndex}`, isImageSlide(slide) && slide.src].filter(Boolean).join("|")));
210
+ if (!slide)
211
+ return null;
212
+ return (jsx(CarouselSlide, { rect: rect, slide: slide, slideIndex: slideIndex, current: slideIndex === index }, slide.key ?? (isImageSlide(slide) ? `${slideIndex}|${slide.src}` : `${slideIndex}`)));
193
213
  }) }));
194
214
  }
195
215
 
@@ -198,31 +218,38 @@ const useController = makeUseContext(ControllerContext);
198
218
  function Controller({ setIndex, children }) {
199
219
  const { slides, index } = useLightboxContext();
200
220
  const exitHooks = useRef([]);
221
+ const closing = useRef(false);
222
+ const addExitHook = useCallback((hook) => {
223
+ exitHooks.current.push(hook);
224
+ return () => {
225
+ exitHooks.current = exitHooks.current.filter((h) => h !== hook);
226
+ };
227
+ }, []);
201
228
  const context = useMemo(() => {
202
229
  const prev = () => {
203
- if (index > 0)
230
+ if (index > 0) {
204
231
  transition(() => setIndex(index - 1));
232
+ }
205
233
  };
206
234
  const next = () => {
207
- if (index < slides.length - 1)
235
+ if (index < slides.length - 1) {
208
236
  transition(() => setIndex(index + 1));
237
+ }
209
238
  };
210
239
  const close = () => {
240
+ if (closing.current)
241
+ return;
242
+ closing.current = true;
211
243
  Promise.all(exitHooks.current.map((hook) => hook()))
212
244
  .catch(() => { })
213
245
  .then(() => {
214
246
  exitHooks.current = [];
215
- setIndex(-1);
247
+ closing.current = false;
248
+ setIndex(undefined);
216
249
  });
217
250
  };
218
- const addExitHook = (hook) => {
219
- exitHooks.current.push(hook);
220
- return () => {
221
- exitHooks.current.splice(exitHooks.current.indexOf(hook), 1);
222
- };
223
- };
224
251
  return { prev, next, close, addExitHook };
225
- }, [slides.length, index, setIndex]);
252
+ }, [slides.length, index, setIndex, addExitHook]);
226
253
  return jsx(ControllerContext.Provider, { value: context, children: children });
227
254
  }
228
255
 
@@ -232,14 +259,11 @@ function Button({ icon: Icon, renderIcon, label, onClick, disabled, className })
232
259
  return (jsx("button", { type: "button", title: buttonLabel, "aria-label": buttonLabel, onClick: onClick, disabled: disabled, style: styles?.button, className: clsx(cssClass("button"), className), children: renderIcon?.() ?? jsx(Icon, { style: styles?.icon, className: cssClass("icon") }) }));
233
260
  }
234
261
 
235
- function svgIcon(name, children) {
236
- const icon = (props) => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", focusable: "false", ...props, children: children }));
262
+ function createIcon(name, glyph) {
263
+ const icon = (props) => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", focusable: "false", "aria-hidden": true, ...props, children: glyph }));
237
264
  icon.displayName = name;
238
265
  return icon;
239
266
  }
240
- function createIcon(name, glyph) {
241
- return svgIcon(name, jsxs("g", { fill: "currentColor", children: [jsx("path", { d: "M0 0h24v24H0z", fill: "none" }), glyph] }));
242
- }
243
267
 
244
268
  const Close = createIcon("Close", jsx("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" }));
245
269
 
@@ -256,13 +280,17 @@ function Navigation() {
256
280
  const WHEEL_ZOOM_FACTOR = 100;
257
281
  const WHEEL_SWIPE_DISTANCE = 100;
258
282
  const WHEEL_SWIPE_COOLDOWN_TIME = 1000;
283
+ const WHEEL_EVENT_HISTORY_WINDOW = 3000;
259
284
  const POINTER_SWIPE_DISTANCE = 100;
260
285
  const KEYBOARD_ZOOM_FACTOR = 8 ** (1 / 4);
261
286
  const KEYBOARD_MOVE_DISTANCE = 50;
262
287
  const PINCH_ZOOM_DISTANCE_FACTOR = 100;
263
288
  const PREVAILING_DIRECTION_FACTOR = 1.2;
289
+ function hasTwoPointers(pointers) {
290
+ return pointers.length === 2;
291
+ }
264
292
  function distance(pointerA, pointerB) {
265
- return ((pointerA.clientX - pointerB.clientX) ** 2 + (pointerA.clientY - pointerB.clientY) ** 2) ** 0.5;
293
+ return Math.hypot(pointerA.clientX - pointerB.clientX, pointerA.clientY - pointerB.clientY);
266
294
  }
267
295
  function useSensors() {
268
296
  const wheelEvents = useRef([]);
@@ -271,7 +299,6 @@ function useSensors() {
271
299
  const activePointers = useRef([]);
272
300
  const pinchZoomDistance = useRef(undefined);
273
301
  const { zoom, maxZoom, changeZoom, changeOffsets } = useZoom();
274
- const { carouselRef } = useZoomInternal();
275
302
  const { prev, next, close } = useController();
276
303
  const { closeOnPullUp, closeOnPullDown, closeOnBackdropClick } = {
277
304
  closeOnPullUp: true,
@@ -279,188 +306,171 @@ function useSensors() {
279
306
  closeOnBackdropClick: true,
280
307
  ...useLightboxContext().controller,
281
308
  };
282
- return useMemo(() => {
283
- const onKeyDown = (event) => {
284
- const { key, metaKey, ctrlKey } = event;
285
- const meta = metaKey || ctrlKey;
286
- const preventDefault = () => event.preventDefault();
287
- const handleChangeZoom = (newZoom) => {
309
+ const addPointer = (event) => {
310
+ removePointer(event);
311
+ activePointers.current.push(event);
312
+ };
313
+ const removePointer = (event) => {
314
+ activePointers.current = activePointers.current.filter((pointer) => pointer.pointerId !== event.pointerId);
315
+ };
316
+ const shouldIgnoreEvent = (event) => ("pointerType" in event && event.pointerType === "mouse" && event.buttons > 1) ||
317
+ (event.target instanceof Element &&
318
+ event.target.closest(`.${cssClass("button")}, .${cssClass("icon")}, .${cssClass("toolbar")}, .${cssClass("selectable")}`) !== null);
319
+ const onKeyDown = useEventCallback((event) => {
320
+ const { key, metaKey, ctrlKey } = event;
321
+ const meta = metaKey || ctrlKey;
322
+ const preventDefault = () => event.preventDefault();
323
+ const handleChangeZoom = (newZoom) => {
324
+ preventDefault();
325
+ changeZoom(newZoom);
326
+ };
327
+ if (key === "+" || (meta && key === "="))
328
+ handleChangeZoom(zoom * KEYBOARD_ZOOM_FACTOR);
329
+ if (key === "-" || (meta && key === "_"))
330
+ handleChangeZoom(zoom / KEYBOARD_ZOOM_FACTOR);
331
+ if (meta && key === "0")
332
+ handleChangeZoom(1);
333
+ if (key === "Escape")
334
+ close();
335
+ if (zoom > 1) {
336
+ const move = (deltaX, deltaY) => {
288
337
  preventDefault();
289
- changeZoom(newZoom);
338
+ changeOffsets(deltaX, deltaY);
290
339
  };
291
- if (key === "+" || (meta && key === "="))
292
- handleChangeZoom(zoom * KEYBOARD_ZOOM_FACTOR);
293
- if (key === "-" || (meta && key === "_"))
294
- handleChangeZoom(zoom / KEYBOARD_ZOOM_FACTOR);
295
- if (meta && key === "0")
296
- handleChangeZoom(1);
297
- if (key === "Escape")
298
- close();
299
- if (zoom > 1) {
300
- const move = (deltaX, deltaY) => {
301
- preventDefault();
302
- changeOffsets(deltaX, deltaY);
303
- };
304
- if (key === "ArrowUp")
305
- move(0, KEYBOARD_MOVE_DISTANCE);
306
- if (key === "ArrowDown")
307
- move(0, -KEYBOARD_MOVE_DISTANCE);
308
- if (key === "ArrowLeft")
309
- move(KEYBOARD_MOVE_DISTANCE, 0);
310
- if (key === "ArrowRight")
311
- move(-KEYBOARD_MOVE_DISTANCE, 0);
312
- return;
313
- }
340
+ if (key === "ArrowUp")
341
+ move(0, KEYBOARD_MOVE_DISTANCE);
342
+ if (key === "ArrowDown")
343
+ move(0, -KEYBOARD_MOVE_DISTANCE);
314
344
  if (key === "ArrowLeft")
315
- prev();
345
+ move(KEYBOARD_MOVE_DISTANCE, 0);
316
346
  if (key === "ArrowRight")
317
- next();
318
- };
319
- const removePointer = (event) => {
320
- const pointers = activePointers.current;
321
- pointers.splice(0, pointers.length, ...pointers.filter((pointer) => pointer.pointerId !== event.pointerId));
322
- };
323
- const addPointer = (event) => {
324
- event.persist();
325
- removePointer(event);
326
- activePointers.current.push(event);
327
- };
328
- const shouldIgnoreEvent = (event) => ("pointerType" in event && event.pointerType === "mouse" && event.buttons > 1) ||
329
- (event.target instanceof Element &&
330
- (event.target.classList.contains(cssClass("button")) ||
331
- event.target.classList.contains(cssClass("icon")) ||
332
- Array.from(carouselRef.current?.parentElement?.querySelectorAll(`.${cssClass("toolbar")}, .${cssClass("selectable")}`) || []).find((element) => element.contains(event.target)) !== undefined));
333
- const onPointerDown = (event) => {
334
- if (shouldIgnoreEvent(event))
335
- return;
347
+ move(-KEYBOARD_MOVE_DISTANCE, 0);
348
+ return;
349
+ }
350
+ if (key === "ArrowLeft") {
351
+ preventDefault();
352
+ prev();
353
+ }
354
+ if (key === "ArrowRight") {
355
+ preventDefault();
356
+ next();
357
+ }
358
+ });
359
+ const onPointerDown = useEventCallback((event) => {
360
+ if (shouldIgnoreEvent(event))
361
+ return;
362
+ addPointer(event);
363
+ if (hasTwoPointers(activePointers.current)) {
364
+ pinchZoomDistance.current = distance(activePointers.current[0], activePointers.current[1]);
365
+ }
366
+ });
367
+ const onPointerMove = useEventCallback((event) => {
368
+ const activePointer = activePointers.current.find((pointer) => pointer.pointerId === event.pointerId);
369
+ if (!activePointer)
370
+ return;
371
+ if (hasTwoPointers(activePointers.current) && pinchZoomDistance.current) {
336
372
  addPointer(event);
337
- const pointers = activePointers.current;
338
- if (pointers.length === 2) {
339
- pinchZoomDistance.current = distance(pointers[0], pointers[1]);
373
+ const currentDistance = distance(activePointers.current[0], activePointers.current[1]);
374
+ const delta = currentDistance - pinchZoomDistance.current;
375
+ if (Math.abs(delta) > 0) {
376
+ changeZoom(scaleZoom(zoom, delta, PINCH_ZOOM_DISTANCE_FACTOR), {
377
+ clientX: (activePointers.current[0].clientX + activePointers.current[1].clientX) / 2,
378
+ clientY: (activePointers.current[0].clientY + activePointers.current[1].clientY) / 2,
379
+ });
380
+ pinchZoomDistance.current = currentDistance;
340
381
  }
341
- };
342
- const onPointerMove = (event) => {
343
- const pointers = activePointers.current;
344
- const activePointer = pointers.find((pointer) => pointer.pointerId === event.pointerId);
345
- if (!activePointer)
346
- return;
347
- if (pointers.length === 2 && pinchZoomDistance.current) {
348
- addPointer(event);
349
- const currentDistance = distance(pointers[0], pointers[1]);
350
- const delta = currentDistance - pinchZoomDistance.current;
351
- if (Math.abs(delta) > 0) {
352
- changeZoom(scaleZoom(zoom, delta, PINCH_ZOOM_DISTANCE_FACTOR), {
353
- clientX: (pointers[0].clientX + pointers[1].clientX) / 2,
354
- clientY: (pointers[0].clientY + pointers[1].clientY) / 2,
355
- });
356
- pinchZoomDistance.current = currentDistance;
357
- }
358
- return;
359
- }
360
- if (zoom > 1) {
361
- if (pointers.length === 1) {
362
- changeOffsets(event.clientX - activePointer.clientX, event.clientY - activePointer.clientY);
363
- }
364
- addPointer(event);
365
- }
366
- };
367
- const onPointerUp = (event) => {
368
- const pointers = activePointers.current;
369
- const activePointer = pointers.find((pointer) => pointer.pointerId === event.pointerId);
370
- if (!activePointer)
371
- return;
372
- if (pointers.length === 1 && zoom === 1) {
373
- const dx = event.clientX - activePointer.clientX;
374
- const dy = event.clientY - activePointer.clientY;
375
- const deltaX = Math.abs(dx);
376
- const deltaY = Math.abs(dy);
377
- if (deltaX > POINTER_SWIPE_DISTANCE && deltaX > PREVAILING_DIRECTION_FACTOR * deltaY) {
378
- if (dx > 0) {
379
- prev();
380
- }
381
- else {
382
- next();
383
- }
384
- }
385
- else if ((deltaY > POINTER_SWIPE_DISTANCE &&
386
- deltaY > PREVAILING_DIRECTION_FACTOR * deltaX &&
387
- ((closeOnPullUp && dy < 0) || (closeOnPullDown && dy > 0))) ||
388
- (closeOnBackdropClick &&
389
- activePointer.target instanceof Element &&
390
- Array.from(activePointer.target.classList).some((className) => [cssClass("slide"), cssClass("portal")].includes(className)))) {
391
- close();
392
- }
393
- }
394
- removePointer(event);
395
- };
396
- const onWheel = (event) => {
397
- if (event.ctrlKey) {
398
- if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
399
- changeZoom(scaleZoom(zoom, -event.deltaY, WHEEL_ZOOM_FACTOR), event);
400
- }
401
- return;
402
- }
403
- if (zoom > 1) {
404
- changeOffsets(-event.deltaX, -event.deltaY);
405
- return;
406
- }
407
- if (wheelCooldown.current && wheelCooldownMomentum.current) {
408
- if (event.deltaX * wheelCooldownMomentum.current > 0 &&
409
- (event.timeStamp <= wheelCooldown.current + WHEEL_SWIPE_COOLDOWN_TIME / 2 ||
410
- (event.timeStamp <= wheelCooldown.current + WHEEL_SWIPE_COOLDOWN_TIME &&
411
- Math.abs(event.deltaX) < PREVAILING_DIRECTION_FACTOR * Math.abs(wheelCooldownMomentum.current)))) {
412
- wheelCooldownMomentum.current = event.deltaX;
413
- return;
414
- }
415
- wheelCooldown.current = null;
416
- wheelCooldownMomentum.current = null;
382
+ return;
383
+ }
384
+ if (zoom > 1) {
385
+ if (activePointers.current.length === 1) {
386
+ changeOffsets(event.clientX - activePointer.clientX, event.clientY - activePointer.clientY);
417
387
  }
418
- event.persist();
419
- wheelEvents.current = wheelEvents.current.filter((e) => e.timeStamp > event.timeStamp - 3000);
420
- wheelEvents.current.push(event);
421
- const dx = wheelEvents.current.map((e) => e.deltaX).reduce((a, b) => a + b, 0);
388
+ addPointer(event);
389
+ }
390
+ });
391
+ const onPointerUp = useEventCallback((event) => {
392
+ const activePointer = activePointers.current.find((pointer) => pointer.pointerId === event.pointerId);
393
+ if (!activePointer)
394
+ return;
395
+ if (activePointers.current.length === 1 && zoom === 1) {
396
+ const dx = event.clientX - activePointer.clientX;
397
+ const dy = event.clientY - activePointer.clientY;
422
398
  const deltaX = Math.abs(dx);
423
- const deltaY = Math.abs(wheelEvents.current.map((e) => e.deltaY).reduce((a, b) => a + b, 0));
424
- if (deltaX > WHEEL_SWIPE_DISTANCE && deltaX > PREVAILING_DIRECTION_FACTOR * deltaY) {
425
- if (dx < 0) {
399
+ const deltaY = Math.abs(dy);
400
+ if (deltaX > POINTER_SWIPE_DISTANCE && deltaX > PREVAILING_DIRECTION_FACTOR * deltaY) {
401
+ if (dx > 0) {
426
402
  prev();
427
403
  }
428
404
  else {
429
405
  next();
430
406
  }
431
- wheelEvents.current = [];
432
- wheelCooldown.current = event.timeStamp;
433
- wheelCooldownMomentum.current = event.deltaX;
434
407
  }
435
- };
436
- const onDoubleClick = (event) => {
437
- if (shouldIgnoreEvent(event))
408
+ else if ((deltaY > POINTER_SWIPE_DISTANCE &&
409
+ deltaY > PREVAILING_DIRECTION_FACTOR * deltaX &&
410
+ ((closeOnPullUp && dy < 0) || (closeOnPullDown && dy > 0))) ||
411
+ (closeOnBackdropClick &&
412
+ activePointer.target instanceof Element &&
413
+ (activePointer.target.classList.contains(cssClass("slide")) ||
414
+ activePointer.target.classList.contains(cssClass("portal"))))) {
415
+ close();
416
+ }
417
+ }
418
+ removePointer(event);
419
+ });
420
+ const onWheel = useEventCallback((event) => {
421
+ if (event.ctrlKey) {
422
+ if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
423
+ changeZoom(scaleZoom(zoom, -event.deltaY, WHEEL_ZOOM_FACTOR), event);
424
+ }
425
+ return;
426
+ }
427
+ if (zoom > 1) {
428
+ changeOffsets(-event.deltaX, -event.deltaY);
429
+ return;
430
+ }
431
+ if (wheelCooldown.current && wheelCooldownMomentum.current) {
432
+ if (event.deltaX * wheelCooldownMomentum.current > 0 &&
433
+ (event.timeStamp <= wheelCooldown.current + WHEEL_SWIPE_COOLDOWN_TIME / 2 ||
434
+ (event.timeStamp <= wheelCooldown.current + WHEEL_SWIPE_COOLDOWN_TIME &&
435
+ Math.abs(event.deltaX) < PREVAILING_DIRECTION_FACTOR * Math.abs(wheelCooldownMomentum.current)))) {
436
+ wheelCooldownMomentum.current = event.deltaX;
438
437
  return;
439
- changeZoom(zoom < maxZoom ? scaleZoom(zoom, 2, 1) : 1, event);
440
- };
441
- return {
442
- onKeyDown,
443
- onPointerDown,
444
- onPointerMove,
445
- onPointerUp,
446
- onPointerLeave: onPointerUp,
447
- onPointerCancel: onPointerUp,
448
- onDoubleClick,
449
- onWheel,
450
- };
451
- }, [
452
- prev,
453
- next,
454
- close,
455
- zoom,
456
- maxZoom,
457
- changeZoom,
458
- changeOffsets,
459
- carouselRef,
460
- closeOnPullUp,
461
- closeOnPullDown,
462
- closeOnBackdropClick,
463
- ]);
438
+ }
439
+ wheelCooldown.current = null;
440
+ wheelCooldownMomentum.current = null;
441
+ }
442
+ wheelEvents.current = wheelEvents.current.filter((e) => e.timeStamp > event.timeStamp - WHEEL_EVENT_HISTORY_WINDOW);
443
+ wheelEvents.current.push(event);
444
+ const dx = wheelEvents.current.map((e) => e.deltaX).reduce((a, b) => a + b, 0);
445
+ const deltaX = Math.abs(dx);
446
+ const deltaY = Math.abs(wheelEvents.current.map((e) => e.deltaY).reduce((a, b) => a + b, 0));
447
+ if (deltaX > WHEEL_SWIPE_DISTANCE && deltaX > PREVAILING_DIRECTION_FACTOR * deltaY) {
448
+ if (dx < 0) {
449
+ prev();
450
+ }
451
+ else {
452
+ next();
453
+ }
454
+ wheelEvents.current = [];
455
+ wheelCooldown.current = event.timeStamp;
456
+ wheelCooldownMomentum.current = event.deltaX;
457
+ }
458
+ });
459
+ const onDoubleClick = useEventCallback((event) => {
460
+ if (shouldIgnoreEvent(event))
461
+ return;
462
+ changeZoom(zoom < maxZoom ? scaleZoom(zoom, 2, 1) : 1, event);
463
+ });
464
+ return useMemo(() => ({
465
+ onKeyDown,
466
+ onPointerDown,
467
+ onPointerMove,
468
+ onPointerUp,
469
+ onPointerLeave: onPointerUp,
470
+ onPointerCancel: onPointerUp,
471
+ onDoubleClick,
472
+ onWheel,
473
+ }), [onKeyDown, onPointerDown, onPointerMove, onPointerUp, onDoubleClick, onWheel]);
464
474
  }
465
475
 
466
476
  function setAttribute(element, attribute, value) {
@@ -480,6 +490,7 @@ function Portal({ children }) {
480
490
  const cleanup = useRef([]);
481
491
  const [mounted, setMounted] = useState(false);
482
492
  const [visible, setVisible] = useState(false);
493
+ const portalRef = useRef(null);
483
494
  const onTransitionEnd = useRef(undefined);
484
495
  const restoreFocus = useRef(null);
485
496
  const sensors = useSensors();
@@ -489,28 +500,36 @@ function Portal({ children }) {
489
500
  cleanup.current = [];
490
501
  }, []);
491
502
  useEffect(() => addExitHook(() => new Promise((resolve) => {
492
- onTransitionEnd.current = () => {
503
+ const transitionDuration = (portalRef.current &&
504
+ Math.max(...getComputedStyle(portalRef.current).transitionDuration.split(",").map(parseFloat)) *
505
+ 1000) ||
506
+ 0;
507
+ const done = () => {
493
508
  onTransitionEnd.current = undefined;
494
509
  resolve();
495
510
  };
511
+ const timeout = setTimeout(done, transitionDuration + 100);
512
+ onTransitionEnd.current = () => {
513
+ clearTimeout(timeout);
514
+ done();
515
+ };
496
516
  handleCleanup();
497
517
  setVisible(false);
498
518
  })), [addExitHook, handleCleanup]);
499
519
  useEffect(() => {
500
520
  const property = cssVar("scrollbar-width");
501
521
  const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
502
- if (scrollbarWidth === 0)
503
- return;
504
- document.documentElement.style.setProperty(property, `${scrollbarWidth}px`);
522
+ if (scrollbarWidth > 0) {
523
+ document.documentElement.style.setProperty(property, `${scrollbarWidth}px`);
524
+ }
525
+ setMounted(true);
505
526
  return () => {
506
527
  document.documentElement.style.removeProperty(property);
528
+ setMounted(false);
507
529
  };
508
530
  }, []);
509
- useEffect(() => {
510
- setMounted(true);
511
- return () => setMounted(false);
512
- }, []);
513
531
  const handleRef = useCallback((node) => {
532
+ portalRef.current = node;
514
533
  if (node) {
515
534
  node.focus();
516
535
  const preventWheelDefaults = (event) => event.preventDefault();
@@ -518,9 +537,7 @@ function Portal({ children }) {
518
537
  cleanup.current.push(() => {
519
538
  node.removeEventListener("wheel", preventWheelDefaults);
520
539
  });
521
- const elements = getChildren(node.parentElement);
522
- for (let i = 0; i < elements.length; i += 1) {
523
- const element = elements[i];
540
+ for (const element of getChildren(node.parentElement)) {
524
541
  if (!["TEMPLATE", "SCRIPT", "STYLE"].includes(element.tagName) && element !== node) {
525
542
  cleanup.current.push(setAttribute(element, "inert", ""));
526
543
  cleanup.current.push(setAttribute(element, "aria-hidden", "true"));
@@ -537,7 +554,7 @@ function Portal({ children }) {
537
554
  }
538
555
  }, [handleCleanup]);
539
556
  return mounted
540
- ? createPortal(jsx("div", { "aria-modal": true, role: "dialog", "aria-label": translateLabel(labels, "Lightbox"), tabIndex: -1, ref: handleRef, style: styles?.portal, className: clsx(cssClass("portal"), !visible && cssClass("portal_closed"), className), onTransitionEnd: () => onTransitionEnd.current?.(), onFocus: (event) => {
557
+ ? createPortal(jsx("div", { "aria-modal": true, role: "dialog", "aria-label": translateLabel(labels, "Lightbox"), tabIndex: -1, ref: handleRef, style: styles?.portal, className: clsx(cssClass("portal"), !visible && cssClass("portal_closed"), className), onTransitionEnd: (event) => event.target === portalRef.current && onTransitionEnd.current?.(), onFocus: (event) => {
541
558
  if (!restoreFocus.current) {
542
559
  restoreFocus.current = event.relatedTarget;
543
560
  }
@@ -548,7 +565,7 @@ function Portal({ children }) {
548
565
  function Toolbar() {
549
566
  const { render: { iconClose } = {}, toolbar: { buttons, fixed } = {}, styles } = useLightboxContext();
550
567
  const { close } = useController();
551
- return (jsxs("div", { style: styles?.toolbar, className: clsx(cssClass("toolbar"), fixed && cssClass("toolbar_fixed")), children: [buttons?.map((button, key) => (isValidElement(button) && !button.key ? cloneElement(button, { key }) : button)), jsx(Button, { label: "Close", icon: Close, renderIcon: iconClose, onClick: close })] }));
568
+ return (jsxs("div", { style: styles?.toolbar, className: clsx(cssClass("toolbar"), fixed && cssClass("toolbar_fixed")), children: [buttons, jsx(Button, { label: "Close", icon: Close, renderIcon: iconClose, onClick: close })] }));
552
569
  }
553
570
 
554
571
  function Lightbox({ slides, index, setIndex, ...rest }) {
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- body:has(>.yarll__portal){overscroll-behavior:none}body:has(>.yarll__portal:not(.yarll__no_scroll_lock)){height:100%;overflow:hidden;padding-right:var(--yarll__scrollbar-width,0)}body:has(>.yarll__portal:not(.yarll__no_scroll_lock)) .yarll_fixed{padding-right:var(--yarll__scrollbar-width,0)}.yarll__portal{align-items:center;display:flex;flex-direction:column;inset:0;justify-content:center;outline:none;overflow:hidden;overscroll-behavior:none;position:fixed;touch-action:none;-moz-user-select:none;user-select:none;-webkit-user-select:none;z-index:var(--yarll__portal_zindex,9999);-webkit-touch-callout:none;background-color:var(--yarll__backdrop_color,#000);color:var(--yarll__color,#fff);opacity:1;transition:var(--yarll__fade_transition,opacity .3s ease)}.yarll__portal_closed{opacity:0}.yarll__portal *{box-sizing:border-box}.yarll__carousel{align-self:stretch;flex:1;margin:var(--yarll__carousel_margin,16px);position:relative}.yarll__slide{align-items:center;display:flex;flex-direction:column;inset:0;justify-content:center;position:absolute}.yarll__slide[hidden]{display:none}.yarll__slide_image{display:block;max-height:100%;max-width:100%;min-height:0;min-width:0;-o-object-fit:contain;object-fit:contain}.yarll__toolbar{align-items:center;display:flex;position:absolute;right:var(--yarll__toolbar_margin,8px);top:var(--yarll__toolbar_margin,8px);z-index:1}.yarll__toolbar_fixed{align-self:flex-end;margin-inline-end:var(--yarll__toolbar_margin,8px);position:static}.yarll__toolbar_fixed,.yarll__toolbar_fixed+.yarll__carousel{margin-block-start:var(--yarll__toolbar_margin,8px)}.yarll__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarll__button_background_color,transparent);border:0;color:var(--yarll__button_color,var(--yarll__button_color,hsla(0,0%,100%,.8)));cursor:pointer;filter:var(--yarll__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));margin:0;padding:var(--yarll__button_padding,8px)}.yarll__button:focus-visible{box-shadow:var(--yarll__button_focus_box_shadow,0 0 0 4px #fff);color:var(--yarll__button_color_active,#fff);outline:var(--yarll__button_focus_outline,6px double #000)}@supports not selector(:focus-visible){.yarll__button:focus{box-shadow:var(--yarll__button_focus_box_shadow,0 0 0 4px #fff);color:var(--yarll__button_color_active,#fff);outline:var(--yarll__button_focus_outline,6px double #000)}}@media (hover:hover){.yarll__button:focus-visible:hover,.yarll__button:focus:hover,.yarll__button:hover{color:var(--yarll__button_color_active,#fff)}}.yarll__button_next,.yarll__button_prev{padding:var(--yarll__navigation_button_padding,24px 8px);position:absolute}.yarll__button_prev{left:8px}.yarll__button_next{right:8px}.yarll__button:disabled{color:var(--yarll__button_color_disabled,var(--yarll__button_color_disabled,hsla(0,0%,100%,.4)));cursor:default}.yarll__icon{display:block;height:var(--yarll__icon_size,32px);pointer-events:none;width:var(--yarll__icon_size,32px)}.yarll__selectable{-moz-user-select:text;user-select:text;-webkit-user-select:text}
1
+ html:has(body>.yarll__portal){overscroll-behavior:none}@media (prefers-reduced-motion:reduce){html:has(body>.yarll__portal)::view-transition-new(*),html:has(body>.yarll__portal)::view-transition-old(*){animation-duration:0s}}body:has(>.yarll__portal:not(.yarll__no_scroll_lock)){height:100%;overflow:hidden;padding-right:var(--yarll__scrollbar-width,0)}body:has(>.yarll__portal:not(.yarll__no_scroll_lock)) .yarll__fixed{padding-right:var(--yarll__scrollbar-width,0)}.yarll__portal{align-items:center;display:flex;flex-direction:column;inset:0;justify-content:center;outline:none;overflow:hidden;overscroll-behavior:none;position:fixed;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:var(--yarll__portal_zindex,9999);-webkit-touch-callout:none;background-color:var(--yarll__backdrop_color,#000);color:var(--yarll__color,#fff);opacity:1;transition:var(--yarll__fade_transition,opacity .3s ease)}@media (prefers-reduced-motion:reduce){.yarll__portal{transition-duration:0s}}.yarll__portal_closed{opacity:0}.yarll__portal *{box-sizing:border-box}.yarll__carousel{align-self:stretch;flex:1;margin:var(--yarll__carousel_margin,16px);position:relative}.yarll__slide{align-items:center;display:flex;flex-direction:column;inset:0;justify-content:center;position:absolute}.yarll__slide[hidden]{display:none}.yarll__slide_image{display:block;max-height:100%;max-width:100%;min-height:0;min-width:0;-o-object-fit:contain;object-fit:contain}.yarll__toolbar{align-items:center;display:flex;position:absolute;right:var(--yarll__toolbar_margin,8px);top:var(--yarll__toolbar_margin,8px);z-index:1}.yarll__toolbar_fixed{align-self:flex-end;margin-inline-end:var(--yarll__toolbar_margin,8px);position:static}.yarll__toolbar_fixed,.yarll__toolbar_fixed+.yarll__carousel{margin-block-start:var(--yarll__toolbar_margin,8px)}.yarll__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarll__button_background_color,transparent);border:0;color:var(--yarll__button_color,hsla(0,0%,100%,.8));cursor:pointer;filter:var(--yarll__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));margin:0;padding:var(--yarll__button_padding,8px)}.yarll__button:focus-visible{box-shadow:var(--yarll__button_focus_box_shadow,0 0 0 4px #fff);color:var(--yarll__button_color_active,#fff);outline:var(--yarll__button_focus_outline,6px double #000)}@supports not selector(:focus-visible){.yarll__button:focus{box-shadow:var(--yarll__button_focus_box_shadow,0 0 0 4px #fff);color:var(--yarll__button_color_active,#fff);outline:var(--yarll__button_focus_outline,6px double #000)}}@media (hover:hover){.yarll__button:focus-visible:hover,.yarll__button:focus:hover,.yarll__button:hover{color:var(--yarll__button_color_active,#fff)}}.yarll__button_next,.yarll__button_prev{padding:var(--yarll__navigation_button_padding,24px 8px);position:absolute}.yarll__button_prev{left:8px}.yarll__button_next{right:8px}.yarll__button:disabled{color:var(--yarll__button_color_disabled,hsla(0,0%,100%,.4));cursor:default}.yarll__icon{display:block;fill:currentColor;height:var(--yarll__icon_size,32px);pointer-events:none;width:var(--yarll__icon_size,32px)}.yarll__selectable{-webkit-user-select:text;-moz-user-select:text;user-select:text}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox-lite",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "description": "Lightweight React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",
@@ -12,10 +12,7 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "default": "./dist/index.js"
14
14
  },
15
- "./styles.css": {
16
- "types": "./dist/styles.css.d.ts",
17
- "default": "./dist/styles.css"
18
- }
15
+ "./styles.css": "./dist/styles.css"
19
16
  },
20
17
  "files": [
21
18
  "dist"
@@ -24,13 +21,12 @@
24
21
  "*.css"
25
22
  ],
26
23
  "homepage": "https://github.com/igordanchenko/yet-another-react-lightbox-lite",
24
+ "funding": "https://github.com/sponsors/igordanchenko",
25
+ "bugs": "https://github.com/igordanchenko/yet-another-react-lightbox-lite/issues",
27
26
  "repository": {
28
27
  "type": "git",
29
28
  "url": "git+https://github.com/igordanchenko/yet-another-react-lightbox-lite.git"
30
29
  },
31
- "bugs": {
32
- "url": "https://github.com/igordanchenko/yet-another-react-lightbox-lite/issues"
33
- },
34
30
  "engines": {
35
31
  "node": ">=18"
36
32
  },
@@ -1,2 +0,0 @@
1
- declare const styles: unknown;
2
- export default styles;