react-pic-gallery 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Small, accessible React image gallery and lightbox with a polished default UI and typed escape hatches for custom controls.
4
4
 
5
5
  [![NPM](https://img.shields.io/npm/v/react-pic-gallery.svg)](https://www.npmjs.com/package/react-pic-gallery)
6
- [![Minified + gzip size](https://badgen.net/static/minified%20%2B%20gzip/8.5%20KB/blue)](https://bundlephobia.com/package/react-pic-gallery)
6
+ [![Minified + gzip size](https://badgen.net/static/minified%20%2B%20gzip/9.6%20KB/blue)](https://bundlephobia.com/package/react-pic-gallery)
7
7
 
8
8
  [Live playground and docs](https://marcelrsoub.github.io/react-pic-gallery/)
9
9
 
@@ -23,17 +23,17 @@ bun add react-pic-gallery
23
23
 
24
24
  React 18.3+ and React 19 are supported. The [Quick start docs](https://marcelrsoub.github.io/react-pic-gallery/docs/getting-started/) provide these commands in a package-manager switcher.
25
25
 
26
- The package is intentionally lightweight: it has no runtime dependencies and React is a peer dependency. The built JavaScript and CSS together are about **8.5 KB gzipped** (6.13 KB JS + 2.34 KB CSS).
26
+ The package is intentionally lightweight: it has no runtime dependencies and React is a peer dependency. The built JavaScript and CSS together are about **9.6 KB gzipped** (7.20 KB JS + 2.40 KB CSS).
27
27
 
28
28
  ## Why react-pic-gallery
29
29
 
30
- - **Tiny by design** — no runtime dependencies, no context providers, no polyfills; ~8.5 KB gzipped total.
30
+ - **Tiny by design** — no runtime dependencies, no context providers, no polyfills; ~9.6 KB gzipped total.
31
31
  - **Simple by default** — one component, one stylesheet import, sensible accessible defaults.
32
32
  - **Yours when needed** — typed `renderActions`, `renderCaption`, and `renderControls` callbacks let you add custom UI without rebuilding the lightbox; extra fields on your image objects flow through fully typed.
33
33
  - **Accessible** — native modal `<dialog>`, focus containment and restoration, Escape to close, screen-reader announcements, `prefers-reduced-motion` support.
34
34
  - **Keyboard and touch friendly** — arrow-key navigation in the grid (roving tabindex) and in the lightbox, Enter to open, swipe navigation on touch devices.
35
35
  - **Themeable** — namespaced classes and CSS variables (`--gallery-accent`, `--gallery-overlay`, `--gallery-motion`, …).
36
- - **Three layouts** — choose a uniform grid, proportional justified rows, or an editorial mosaic.
36
+ - **Four layouts** — choose a uniform grid, proportional justified rows, an editorial mosaic, or an inline carousel.
37
37
 
38
38
  ## Quick start
39
39
 
@@ -43,8 +43,12 @@ import 'react-pic-gallery/styles.css'
43
43
 
44
44
  const images = [
45
45
  {
46
- src: 'https://example.com/photo-large.jpg',
47
- thumbnailSrc: 'https://example.com/photo-thumb.jpg',
46
+ src: 'https://example.com/mountain-1600.jpg',
47
+ srcSet: 'https://example.com/mountain-960.jpg 960w, https://example.com/mountain-1600.jpg 1600w',
48
+ sizes: '(max-width: 48rem) 100vw, 80rem',
49
+ thumbnailSrc: 'https://example.com/mountain-thumb-480.jpg',
50
+ thumbnailSrcSet: 'https://example.com/mountain-thumb-320.jpg 320w, https://example.com/mountain-thumb-640.jpg 640w',
51
+ thumbnailSizes: '(max-width: 48rem) 100vw, 33vw',
48
52
  alt: 'A mountain reflected in a lake',
49
53
  caption: 'Morning at the lake'
50
54
  }
@@ -55,7 +59,7 @@ export function App() {
55
59
  }
56
60
  ```
57
61
 
58
- `src` and `alt` are required. `id`, `thumbnailSrc`, `caption`, `width`, and `height` are optional. Image objects can include application-specific fields; those fields remain available in renderer callbacks when using TypeScript generics.
62
+ `src` and `alt` are required. `srcSet` and `sizes` describe responsive full-size sources; `thumbnailSrc`, `thumbnailSrcSet`, and `thumbnailSizes` optionally provide a separate responsive thumbnail family. If neither `thumbnailSrc` nor `thumbnailSrcSet` is provided, thumbnails use the full-size source family. `id`, `caption`, `width`, and `height` are optional. Image objects can include application-specific fields; those fields remain available in renderer callbacks when using TypeScript generics.
59
63
 
60
64
  ## Gallery layouts
61
65
 
@@ -64,13 +68,17 @@ The default is the existing three-column grid. Choose another layout with `layou
64
68
  ```tsx
65
69
  <PicGallery images={images} layout='justified' />
66
70
  <PicGallery images={images} layout='mosaic' />
71
+ <PicGallery images={images} layout='carousel' />
67
72
  ```
68
73
 
69
74
  - **`grid`** — consistent tiles with a configurable fixed column count.
70
75
  - **`justified`** — proportional photos arranged in aligned rows; the final row stays left-aligned.
71
76
  - **`mosaic`** — alternating featured photos and smaller supporting tiles.
77
+ - **`carousel`** — one image at a time with inline previous/next controls.
72
78
 
73
- Image `width` and `height` are recommended, but optional. Justified rows use them when supplied and fall back to 3:2 when either dimension is missing or invalid. Accurate dimensions give the most faithful layout and help reserve space while images load. `rowHeight` sets the tile height for the grid, the base row height for the mosaic, and the target row height for justified galleries. `columns` applies to the grid only.
79
+ The default appearance is `framed`; use `appearance='bare'` to remove the outer surface while retaining tile spacing and rounded corners. Image `width` and `height` are recommended, but optional. Justified rows use them when supplied and fall back to 3:2 when either dimension is missing or invalid. Accurate dimensions give the most faithful layout and help reserve space while images load. `rowHeight` sets the tile height for the grid, the base row height for the mosaic, and the target row height for justified galleries. `columns` applies to the grid only and defaults to three.
80
+
81
+ Responsive image selection uses native `srcSet` and `sizes`. A dedicated thumbnail family is selected when either `thumbnailSrc` or `thumbnailSrcSet` is set; otherwise thumbnails use `src`, `srcSet`, and `sizes`. For example, `srcSet` alone works as a responsive source for both thumbnails and the lightbox. If `thumbnailSrcSet` is supplied without `thumbnailSrc`, `src` is used as its fallback URL.
74
82
 
75
83
  ## Custom UI
76
84
 
@@ -131,6 +139,7 @@ The lightbox uses the native modal `<dialog>` element and includes:
131
139
  - Keyboard navigation, Escape-to-close, focus containment, and focus restoration.
132
140
  - Backdrop closing with stable page width while scrolling is locked.
133
141
  - Native lazy loading for thumbnails and explicit image loading/error states.
142
+ - Adjacent full-size images are preloaded while the viewer is active, using responsive sources and low fetch priority.
134
143
  - Horizontal swipe navigation on touch devices.
135
144
  - Instagram-Stories-style edge taps on touch screens: tap the right edge of the image to go forward, the left edge to go back (navigation buttons are hidden on small screens where tap zones take over).
136
145
  - Reduced-motion support, safe-area padding, rounded image surfaces, and animated transitions.
@@ -139,21 +148,34 @@ Gallery tiles use roving focus. Left/Right follow image order; in the grid, Up/D
139
148
 
140
149
  Native modal behavior targets modern browsers: Chrome 37+, Edge 79+, Firefox 98+, and Safari/iOS 15.4+. The package does not ship a dialog polyfill.
141
150
 
151
+ Adjacent preloading is enabled by default and considers only the immediate previous and next full-size images while the lightbox is open or the inline carousel is mounted. The shared tracker is capped at 32 entries. Set `preloadAdjacent={false}` to opt out. It uses the browser image cache and does not guarantee reuse; cache headers, browser policy, and network conditions still apply.
152
+
142
153
  ## Styling
143
154
 
144
- The stylesheet uses namespaced classes and CSS variables. Import it once, then override variables globally or on the lightbox class:
155
+ The stylesheet uses namespaced classes and CSS variables. Import it once. Set gallery variables on the `PicGallery` wrapper (for example, through its `className`) so the layout container inherits them; the lightbox is portaled, so lightbox colors should be set globally or on `.react-pic-gallery__lightbox`.
145
156
 
146
157
  ```css
147
- :root,
148
- .react-pic-gallery__lightbox {
158
+ .brand-gallery {
159
+ --gallery-background: #111a24;
160
+ --gallery-padding: 0.65rem;
161
+ --gallery-border: 1px solid rgba(148, 163, 184, 0.18);
162
+ --gallery-frame-radius: 1.1rem;
149
163
  --gallery-accent: #ff7a59;
164
+ }
165
+
166
+ .react-pic-gallery__lightbox {
150
167
  --gallery-overlay: rgba(10, 10, 14, 0.98);
151
168
  --gallery-control-size: 3rem;
152
169
  --gallery-motion: 240ms;
153
170
  }
154
171
  ```
155
172
 
156
- The default grid uses three columns. Pass `columns` for a different fixed count. `rowHeight` accepts CSS height values or numeric pixels; its meaning depends on the selected layout as described above.
173
+ ```tsx
174
+ <PicGallery className='brand-gallery' images={images} />
175
+ <PicGallery className='brand-gallery' images={images} appearance='bare' />
176
+ ```
177
+
178
+ The default frame variables are `--gallery-background`, `--gallery-padding`, `--gallery-border`, and `--gallery-frame-radius`. The default grid uses three columns. Pass `columns` for a different fixed count. `rowHeight` accepts CSS height values or numeric pixels; its meaning depends on the selected layout as described above.
157
179
 
158
180
  ## API
159
181
 
@@ -162,23 +184,40 @@ The default grid uses three columns. Pass `columns` for a different fixed count.
162
184
  | Prop | Type | Description |
163
185
  | --- | --- | --- |
164
186
  | `images` | `readonly GalleryImage[]` | Images to display. |
165
- | `layout` | `'grid' \| 'justified' \| 'mosaic'` | Defaults to `'grid'`. |
187
+ | `layout` | `'grid' \| 'justified' \| 'mosaic' \| 'carousel'` | Defaults to `'grid'`. |
188
+ | `appearance` | `'framed' \| 'bare'` | Defaults to `'framed'`. |
166
189
  | `columns` | `number` | Optional fixed grid column count. Defaults to three; only applies to `grid`. |
167
190
  | `rowHeight` | `CSSProperties['height']` | Grid tile height, mosaic base row height, or justified target row height. |
191
+ | `preloadAdjacent` | `boolean` | Defaults to `true`. Preloads adjacent full-size images while the lightbox is open, and for the inline carousel while it is mounted. |
168
192
  | `renderActions` | `LightboxRenderer` | Adds controls to the default toolbar. |
169
193
  | `renderCaption` | `LightboxRenderer` | Replaces the current caption. |
170
194
  | `renderControls` | `LightboxRenderer` | Replaces the complete default control layer. |
171
195
  | `showCounter` | `boolean` | Shows the current image count. Defaults to `true`. |
172
196
  | `showNavigation` | `boolean` | Shows previous/next controls. Defaults to `true`. |
173
197
 
174
- `Gallery` accepts `images`, `onImageClick`, `layout`, `columns`, `rowHeight`, `className`, and `style`.
198
+ `Gallery` accepts `images`, `onImageClick`, `layout`, `appearance`, `columns`, `rowHeight`, `preloadAdjacent`, `className`, and `style`. Its appearance defaults to `framed`; adjacent-image preloading applies only when `layout='carousel'`.
199
+
200
+ `Lightbox` accepts `images`, controlled `index`, `onIndexChange`, the renderer props, `showCounter`, `showNavigation`, `preloadAdjacent`, and `className`.
201
+
202
+ `PicGallery` accepts `appearance` (`'framed' | 'bare'`, default `'framed'`) and `preloadAdjacent` in addition to the props above. Adjacent preloading is enabled by default while the lightbox is open or the inline carousel is mounted. It preloads only the immediate previous and next full-size responsive sources, uses low fetch priority, and tracks at most 32 preload entries. Set `preloadAdjacent={false}` to opt out. Browser caching and network policy determine whether a prefetched response is reused.
203
+
204
+ ### `GalleryImage`
175
205
 
176
- `Lightbox` accepts `images`, controlled `index`, `onIndexChange`, the renderer props, `showCounter`, `showNavigation`, and `className`.
206
+ | Field | Type | Description |
207
+ | --- | --- | --- |
208
+ | `src` | `string` | Required full-size fallback source. |
209
+ | `srcSet` | `string` | Optional responsive full-size candidates, passed to the browser natively. |
210
+ | `sizes` | `string` | Optional rendered-size hint for `srcSet`. |
211
+ | `thumbnailSrc` | `string` | Optional dedicated thumbnail source. |
212
+ | `thumbnailSrcSet` | `string` | Optional responsive candidates for a dedicated thumbnail family. |
213
+ | `thumbnailSizes` | `string` | Optional rendered-size hint for `thumbnailSrcSet`. |
177
214
 
178
215
  ## Migrating from v1
179
216
 
180
217
  v2 intentionally removes the old configuration and external lightbox workaround.
181
218
 
219
+ `Gallery` and `PicGallery` now use the built-in framed appearance by default. If an existing card or `.gallery-frame` wrapper adds its own padding, background, border, or rounded surface, remove those duplicate surface styles and keep only layout or margin rules. Set `appearance='bare'` when you want the previous unframed look; tile spacing and tile corner radii remain in place.
220
+
182
221
  | v1 | v2 |
183
222
  | --- | --- |
184
223
  | `imgList` | `images` |
@@ -222,6 +261,7 @@ npm install
222
261
  npm run dev
223
262
  npm run dev:library
224
263
  npm test
264
+ npm run test:visual
225
265
  npm run typecheck
226
266
  npm run build
227
267
  npm run build:docs
@@ -230,6 +270,22 @@ npm pack --dry-run
230
270
 
231
271
  `npm run dev` starts the Astro playground and docs site. `npm run dev:library` starts the standalone Vite library demo. Publishing runs the package build automatically through `prepack`.
232
272
 
273
+ ### Visual regression tests
274
+
275
+ `npm run test:visual` renders a deterministic fixture (`visual/`) across every layout, the standalone components, and the lightbox at desktop and mobile widths, then compares screenshots and layout metrics against the committed baselines in `visual/baselines/`. `npm run test:visual:update` regenerates those baselines after an intentional visual change.
276
+
277
+ The harness drives a real Chromium over the DevTools protocol, so a browser must be reachable. It defaults to `ws://127.0.0.1:9222/`; override with `VISUAL_CDP`. In this repository's environment, run the browserless Chromium container and set `VISUAL_BASE_HOST` to the host address the container can reach (the container's default gateway, e.g. `192.168.16.1`):
278
+
279
+ ```bash
280
+ VISUAL_BASE_HOST=192.168.16.1 npm run test:visual
281
+ ```
282
+
283
+ `npm run test:visual:cross` renders the current working tree and a prior git ref with the `bare` appearance, which matches the pre-`framed` default, and diffs them to confirm rendering did not change. It accepts `--ref <ref>` (default `HEAD`) and `--tolerance <ratio>` (default `0.0005`, i.e. 0.05% of pixels) for sub-pixel text antialiasing:
284
+
285
+ ```bash
286
+ VISUAL_BASE_HOST=192.168.16.1 npm run test:visual:cross -- --ref HEAD
287
+ ```
288
+
233
289
  ## License
234
290
 
235
291
  MIT © [marcelrsoub](https://github.com/marcelrsoub)
@@ -10,6 +10,7 @@ export type CarouselProps<T extends GalleryImage = GalleryImage> = {
10
10
  renderControls?: LightboxRenderer<T>;
11
11
  showCounter?: boolean;
12
12
  showNavigation?: boolean;
13
+ preloadAdjacent?: boolean;
13
14
  close?: () => void;
14
15
  onEmptyAreaClick?: () => void;
15
16
  onImageClick?: () => void;
package/dist/Gallery.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { GalleryImage, GalleryProps } from './types';
2
- declare function GalleryComponent<T extends GalleryImage>({ images, onImageClick, layout, columns, rowHeight, className, style, renderCaption, showCounter, showNavigation }: GalleryProps<T>): import('react').JSX.Element;
2
+ declare function GalleryComponent<T extends GalleryImage>({ images, onImageClick, layout, appearance, columns, rowHeight, className, style, renderCaption, showCounter, showNavigation, preloadAdjacent }: GalleryProps<T>): import('react').JSX.Element;
3
3
  export declare const Gallery: typeof GalleryComponent;
4
4
  export {};
package/dist/Image.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { AnimationEventHandler, CSSProperties, MouseEventHandler } from 'react';
2
2
  type ImageProps = {
3
3
  src: string;
4
+ srcSet?: string;
5
+ sizes?: string;
4
6
  alt: string;
5
7
  className?: string;
6
8
  imageClassName?: string;
@@ -13,5 +15,5 @@ type ImageProps = {
13
15
  width?: number;
14
16
  height?: number;
15
17
  };
16
- export declare function Image({ src, alt, className, imageClassName, style, imageStyle, onAnimationEnd, onClick, eager, objectFit, width, height }: ImageProps): import('react').JSX.Element;
18
+ export declare function Image({ src, srcSet, sizes, alt, className, imageClassName, style, imageStyle, onAnimationEnd, onClick, eager, objectFit, width, height }: ImageProps): import('react').JSX.Element;
17
19
  export {};
@@ -1,2 +1,2 @@
1
1
  import { GalleryImage, LightboxProps } from './types';
2
- export declare function Lightbox<T extends GalleryImage>({ images, index, onIndexChange, renderActions, renderCaption, renderControls, showCounter, showNavigation, className }: LightboxProps<T>): import('react').ReactPortal | null;
2
+ export declare function Lightbox<T extends GalleryImage>({ images, index, onIndexChange, renderActions, renderCaption, renderControls, showCounter, showNavigation, preloadAdjacent, className }: LightboxProps<T>): import('react').ReactPortal | null;
@@ -1,2 +1,2 @@
1
1
  import { GalleryImage, PicGalleryProps } from './types';
2
- export declare function PicGallery<T extends GalleryImage>({ images, layout, columns, rowHeight, className, galleryClassName, style, renderActions, renderCaption, renderControls, showCounter, showNavigation }: PicGalleryProps<T>): import('react').JSX.Element;
2
+ export declare function PicGallery<T extends GalleryImage>({ images, layout, appearance, columns, rowHeight, className, galleryClassName, style, renderActions, renderCaption, renderControls, showCounter, showNavigation, preloadAdjacent }: PicGalleryProps<T>): import('react').JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { GalleryImage } from './types';
2
+ export type ImageSource = {
3
+ src: string;
4
+ srcSet?: string;
5
+ sizes?: string;
6
+ };
7
+ export declare function getFullImageSource(image: GalleryImage): ImageSource;
8
+ export declare function getThumbnailImageSource(image: GalleryImage): ImageSource;
9
+ export declare function getImageSourceKey(source: ImageSource): string;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { Gallery } from './Gallery';
2
2
  export { Lightbox } from './Lightbox';
3
3
  export { PicGallery } from './PicGallery';
4
- export type { GalleryImage, GalleryLayout, GalleryProps, LightboxContext, LightboxProps, LightboxRenderer, PicGalleryProps } from './types';
4
+ export type { GalleryImage, GalleryAppearance, GalleryLayout, GalleryProps, LightboxContext, LightboxProps, LightboxRenderer, PicGalleryProps } from './types';
5
5
  export { PicGallery as default } from './PicGallery';
@@ -2,41 +2,62 @@ import { memo as e, useCallback as t, useEffect as n, useId as r, useLayoutEffec
2
2
  import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
3
3
  import { createPortal as u } from "react-dom";
4
4
  //#region src/lib/Image.tsx
5
- function d({ src: e, alt: t, className: r = "", imageClassName: i = "", style: s, imageStyle: u, onAnimationEnd: d, onClick: f, eager: p = !1, objectFit: m = "cover", width: h, height: g }) {
6
- let _ = a(null), [v, y] = o("loading");
5
+ function d({ src: e, srcSet: t, sizes: r, alt: i, className: s = "", imageClassName: u = "", style: d, imageStyle: f, onAnimationEnd: p, onClick: m, eager: h = !1, objectFit: g = "cover", width: _, height: v }) {
6
+ let y = a(null), b = JSON.stringify([
7
+ e,
8
+ t ?? null,
9
+ r ?? null
10
+ ]), [x, S] = o({
11
+ sourceKey: b,
12
+ status: "loading"
13
+ }), C = x.sourceKey === b ? x.status : "loading";
7
14
  return n(() => {
8
- y("loading");
9
- let e = _.current;
10
- e?.complete && y(e.naturalWidth > 0 ? "loaded" : "error");
11
- }, [e]), /* @__PURE__ */ l("span", {
12
- className: `react-pic-gallery__image ${r}`.trim(),
13
- style: s,
14
- "aria-busy": v === "loading",
15
- onAnimationEnd: d,
16
- onClick: f,
15
+ S({
16
+ sourceKey: b,
17
+ status: "loading"
18
+ });
19
+ let e = y.current;
20
+ e?.complete && S({
21
+ sourceKey: b,
22
+ status: e.naturalWidth > 0 ? "loaded" : "error"
23
+ });
24
+ }, [b]), /* @__PURE__ */ l("span", {
25
+ className: `react-pic-gallery__image ${s}`.trim(),
26
+ style: d,
27
+ "aria-busy": C === "loading",
28
+ onAnimationEnd: p,
29
+ onClick: m,
17
30
  children: [
18
31
  /* @__PURE__ */ c("img", {
19
- ref: _,
32
+ ref: y,
33
+ sizes: r,
34
+ srcSet: t,
20
35
  src: e,
21
- alt: t,
22
- className: `react-pic-gallery__image-element ${i}`.trim(),
36
+ alt: i,
37
+ className: `react-pic-gallery__image-element ${u}`.trim(),
23
38
  style: {
24
- objectFit: m,
25
- ...u
39
+ objectFit: g,
40
+ ...f
26
41
  },
27
- loading: p ? "eager" : "lazy",
42
+ loading: h ? "eager" : "lazy",
28
43
  decoding: "async",
29
- width: h,
30
- height: g,
31
- onLoad: () => y("loaded"),
32
- onError: () => y("error")
44
+ width: _,
45
+ height: v,
46
+ onLoad: () => S({
47
+ sourceKey: b,
48
+ status: "loaded"
49
+ }),
50
+ onError: () => S({
51
+ sourceKey: b,
52
+ status: "error"
53
+ })
33
54
  }),
34
- v === "loading" && /* @__PURE__ */ c("span", {
55
+ C === "loading" && /* @__PURE__ */ c("span", {
35
56
  className: "react-pic-gallery__image-loader",
36
57
  role: "progressbar",
37
58
  "aria-label": "Loading image"
38
59
  }),
39
- v === "error" && /* @__PURE__ */ c("span", {
60
+ C === "error" && /* @__PURE__ */ c("span", {
40
61
  className: "react-pic-gallery__image-error",
41
62
  role: "status",
42
63
  children: "Unable to load image"
@@ -74,34 +95,126 @@ function g(...e) {
74
95
  return e.filter(Boolean).join(" ");
75
96
  }
76
97
  //#endregion
77
- //#region src/lib/Carousel.tsx
98
+ //#region src/lib/imageSources.ts
78
99
  function _(e) {
79
- let { images: t, index: n, navigation: r, renderActions: i, renderCaption: a, renderControls: o, showCounter: u = !0, showNavigation: f = !0, close: _, onEmptyAreaClick: v, onImageClick: y, className: b } = e, x = t[n], S = {
80
- image: x,
100
+ return {
101
+ src: e.src,
102
+ srcSet: e.srcSet,
103
+ sizes: e.sizes
104
+ };
105
+ }
106
+ function v(e) {
107
+ return e.thumbnailSrc === void 0 && e.thumbnailSrcSet === void 0 ? _(e) : {
108
+ src: e.thumbnailSrc ?? e.src,
109
+ srcSet: e.thumbnailSrcSet,
110
+ sizes: e.thumbnailSizes
111
+ };
112
+ }
113
+ function y(e) {
114
+ return JSON.stringify([
115
+ e.src,
116
+ e.srcSet ?? null,
117
+ e.sizes ?? null
118
+ ]);
119
+ }
120
+ //#endregion
121
+ //#region src/lib/useAdjacentImagePreload.ts
122
+ var b = 32, x = /* @__PURE__ */ new Map();
123
+ function S(e) {
124
+ e.image &&= (e.image.removeEventListener("load", e.onLoad), e.image.removeEventListener("error", e.onError), null);
125
+ }
126
+ function C(e) {
127
+ S(e), x.get(e.key) === e && x.delete(e.key);
128
+ }
129
+ function w() {
130
+ for (; x.size >= b;) {
131
+ let e = [...x.values()].find((e) => e.owners.size === 0);
132
+ if (!e) return !1;
133
+ C(e);
134
+ }
135
+ return !0;
136
+ }
137
+ function T(e) {
138
+ x.delete(e.key), x.set(e.key, e);
139
+ }
140
+ function E(e, t) {
141
+ if (!e.src.trim()) return null;
142
+ let n = y(e), r = x.get(n);
143
+ if (r) return r.owners.add(t), T(r), r;
144
+ if (typeof Image > "u" || !w()) return null;
145
+ let i = new Image(), a = {
146
+ key: n,
147
+ status: "pending",
148
+ image: i,
149
+ owners: /* @__PURE__ */ new Set([t]),
150
+ onLoad: () => {},
151
+ onError: () => {}
152
+ };
153
+ a.onLoad = () => {
154
+ x.get(n) === a && (a.status = "loaded", S(a));
155
+ }, a.onError = () => {
156
+ x.get(n) === a && C(a);
157
+ }, x.set(n, a), i.addEventListener("load", a.onLoad), i.addEventListener("error", a.onError), i.fetchPriority = "low";
158
+ try {
159
+ e.sizes !== void 0 && (i.sizes = e.sizes), e.srcSet !== void 0 && (i.srcset = e.srcSet), i.src = e.src;
160
+ } catch {
161
+ C(a);
162
+ }
163
+ return a;
164
+ }
165
+ function D(e, t) {
166
+ e.owners.delete(t), e.status === "pending" && e.owners.size === 0 && C(e);
167
+ }
168
+ function O(e, t, r) {
169
+ n(() => {
170
+ if (!r || e.length < 2 || t < 0 || t >= e.length) return;
171
+ let n = Symbol("adjacent-image-preload"), i = /* @__PURE__ */ new Map();
172
+ for (let n of [t - 1, t + 1]) {
173
+ if (n < 0 || n >= e.length) continue;
174
+ let t = _(e[n]);
175
+ i.set(y(t), t);
176
+ }
177
+ let a = [...i.values()].map((e) => E(e, n)).filter((e) => e !== null);
178
+ return () => {
179
+ a.forEach((e) => D(e, n));
180
+ };
181
+ }, [
182
+ r,
183
+ e,
184
+ t
185
+ ]);
186
+ }
187
+ //#endregion
188
+ //#region src/lib/Carousel.tsx
189
+ function k(e) {
190
+ let { images: t, index: n, navigation: r, renderActions: i, renderCaption: a, renderControls: o, showCounter: u = !0, showNavigation: f = !0, preloadAdjacent: v = !0, close: y, onEmptyAreaClick: b, onImageClick: x, className: S } = e, C = t[n];
191
+ O(t, n, v);
192
+ let w = C ? _(C) : void 0, T = {
193
+ image: C,
81
194
  index: n,
82
195
  count: t.length,
83
- close: _ ?? (() => {}),
196
+ close: y ?? (() => {}),
84
197
  next: r.next,
85
198
  previous: r.previous,
86
199
  canGoNext: n < t.length - 1,
87
200
  canGoPrevious: n > 0
88
- }, C = f && t.length > 1, w = f && S.canGoPrevious, T = f && S.canGoNext;
201
+ }, E = f && t.length > 1, D = f && T.canGoPrevious, k = f && T.canGoNext;
89
202
  return /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ l("p", {
90
203
  className: "react-pic-gallery__sr-only",
91
204
  "aria-live": "polite",
92
205
  children: [
93
- x.alt,
206
+ C.alt,
94
207
  ", image ",
95
208
  n + 1,
96
209
  " of ",
97
210
  t.length
98
211
  ]
99
212
  }), /* @__PURE__ */ l("div", {
100
- className: g("react-pic-gallery__lightbox-content", b),
213
+ className: g("react-pic-gallery__lightbox-content", S),
101
214
  children: [
102
215
  o ? /* @__PURE__ */ c("div", {
103
216
  className: "react-pic-gallery__custom-controls",
104
- children: o(S)
217
+ children: o(T)
105
218
  }) : /* @__PURE__ */ l("div", {
106
219
  className: "react-pic-gallery__toolbar",
107
220
  children: [/* @__PURE__ */ l("div", {
@@ -113,26 +226,26 @@ function _(e) {
113
226
  " / ",
114
227
  t.length
115
228
  ]
116
- }), i?.(S)]
117
- }), _ && /* @__PURE__ */ c("button", {
229
+ }), i?.(T)]
230
+ }), y && /* @__PURE__ */ c("button", {
118
231
  className: "react-pic-gallery__control",
119
232
  type: "button",
120
- onClick: _,
233
+ onClick: y,
121
234
  "aria-label": "Close image viewer",
122
235
  children: /* @__PURE__ */ c(m, {})
123
236
  })]
124
237
  }),
125
238
  /* @__PURE__ */ l("div", {
126
- className: g("react-pic-gallery__viewport", C ? "react-pic-gallery__viewport--has-navigation" : void 0),
239
+ className: g("react-pic-gallery__viewport", E ? "react-pic-gallery__viewport--has-navigation" : void 0),
127
240
  onMouseDown: (e) => {
128
- e.target === e.currentTarget && v?.();
241
+ e.target === e.currentTarget && b?.();
129
242
  },
130
243
  onClick: r.handleViewportClick,
131
244
  onTouchCancel: r.handleTouchCancel,
132
245
  onTouchEnd: r.handleTouchEnd,
133
246
  onTouchStart: r.handleTouchStart,
134
247
  children: [
135
- w && /* @__PURE__ */ c("button", {
248
+ D && /* @__PURE__ */ c("button", {
136
249
  className: "react-pic-gallery__control react-pic-gallery__control--previous",
137
250
  type: "button",
138
251
  onClick: r.previous,
@@ -140,17 +253,23 @@ function _(e) {
140
253
  children: /* @__PURE__ */ c(h, { direction: "left" })
141
254
  }),
142
255
  /* @__PURE__ */ c(d, {
143
- src: x.src,
144
- alt: x.alt,
256
+ sizes: w?.sizes,
257
+ srcSet: w?.srcSet,
258
+ src: w?.src ?? "",
259
+ alt: C.alt,
145
260
  eager: !0,
146
261
  objectFit: "contain",
147
262
  className: g("react-pic-gallery__lightbox-image", r.swipeDirection ? `react-pic-gallery__lightbox-image--swipe-${r.swipeDirection}` : void 0),
148
263
  onAnimationEnd: r.handleImageAnimationEnd,
149
- onClick: y ? (e) => {
150
- f && t.length > 1 && p(e.clientX, e.currentTarget.parentElement) !== null || y();
264
+ onClick: x ? (e) => {
265
+ f && t.length > 1 && p(e.clientX, e.currentTarget.parentElement) !== null || x();
151
266
  } : void 0
152
- }, x.src),
153
- T && /* @__PURE__ */ c("button", {
267
+ }, w ? JSON.stringify([
268
+ w.src,
269
+ w.srcSet,
270
+ w.sizes
271
+ ]) : n),
272
+ k && /* @__PURE__ */ c("button", {
154
273
  className: "react-pic-gallery__control react-pic-gallery__control--next",
155
274
  type: "button",
156
275
  onClick: r.next,
@@ -159,22 +278,22 @@ function _(e) {
159
278
  })
160
279
  ]
161
280
  }),
162
- (a || x.caption) && /* @__PURE__ */ c("div", {
281
+ (a || C.caption) && /* @__PURE__ */ c("div", {
163
282
  className: "react-pic-gallery__caption",
164
- children: a ? a(S) : x.caption
283
+ children: a ? a(T) : C.caption
165
284
  })
166
285
  ]
167
286
  })] });
168
287
  }
169
288
  //#endregion
170
289
  //#region src/lib/layout.ts
171
- var v = 3 / 2;
172
- function y(e) {
173
- return Number.isFinite(e.width) && Number.isFinite(e.height) && e.width !== void 0 && e.height !== void 0 && e.width > 0 && e.height > 0 ? e.width / e.height : v;
290
+ var A = 3 / 2;
291
+ function j(e) {
292
+ return Number.isFinite(e.width) && Number.isFinite(e.height) && e.width !== void 0 && e.height !== void 0 && e.width > 0 && e.height > 0 ? e.width / e.height : A;
174
293
  }
175
- function b(e, t, n, r) {
294
+ function M(e, t, n, r) {
176
295
  if (e.length === 0) return [];
177
- let i = Math.max(1, t), a = Math.max(1, n), o = Math.max(0, r), s = e.map(y), c = [], l = 0;
296
+ let i = Math.max(1, t), a = Math.max(1, n), o = Math.max(0, r), s = e.map(j), c = [], l = 0;
178
297
  for (; l < e.length;) {
179
298
  let t = l + 1, n = i / s[l], r = Math.abs(n - a), u = s[l];
180
299
  for (let c = l + 1; c < e.length; c += 1) {
@@ -194,7 +313,7 @@ function b(e, t, n, r) {
194
313
  }
195
314
  //#endregion
196
315
  //#region src/lib/useCarouselNavigation.ts
197
- function x(e) {
316
+ function N(e) {
198
317
  let { images: n, index: r, onIndexChange: i, disabled: s = !1, showNavigation: c = !0 } = e, l = a(n), u = a(r), d = a(i), m = a(s), h = a(c), g = a(null), [_, v] = o(null);
199
318
  l.current = n, u.current = r, d.current = i, m.current = s, h.current = c;
200
319
  let y = t(() => {
@@ -251,13 +370,20 @@ function x(e) {
251
370
  }
252
371
  //#endregion
253
372
  //#region src/lib/Gallery.tsx
254
- var S = 3, C = 192, w = 12, T = typeof window > "u" ? n : i;
255
- function E({ images: e, onImageClick: t, renderCaption: n, showCounter: r, showNavigation: i }) {
256
- let [a, s] = o(0), l = Math.max(0, Math.min(a, e.length - 1)), u = x({
373
+ var P = 3, F = 192, I = 12, L = typeof window > "u" ? n : i;
374
+ function R(e) {
375
+ let t = Number.parseFloat(e);
376
+ return Number.isFinite(t) && t >= 0 ? t : null;
377
+ }
378
+ function z(e) {
379
+ return Number.isFinite(e) && e > 0;
380
+ }
381
+ function B({ images: e, onImageClick: t, renderCaption: n, showCounter: r, showNavigation: i, preloadAdjacent: a }) {
382
+ let [s, l] = o(0), u = Math.max(0, Math.min(s, e.length - 1)), d = N({
257
383
  images: e,
258
- index: e.length > 0 ? l : null,
384
+ index: e.length > 0 ? u : null,
259
385
  onIndexChange: (e) => {
260
- e !== null && s(e);
386
+ e !== null && l(e);
261
387
  },
262
388
  showNavigation: i
263
389
  });
@@ -267,39 +393,40 @@ function E({ images: e, onImageClick: t, renderCaption: n, showCounter: r, showN
267
393
  "aria-roledescription": "carousel",
268
394
  "aria-label": "Image carousel",
269
395
  tabIndex: 0,
270
- onKeyDown: u.handleKeyDown,
271
- children: /* @__PURE__ */ c(_, {
396
+ onKeyDown: d.handleKeyDown,
397
+ children: /* @__PURE__ */ c(k, {
272
398
  images: e,
273
- index: l,
274
- navigation: u,
399
+ index: u,
400
+ navigation: d,
275
401
  renderCaption: n,
276
402
  showCounter: r,
277
403
  showNavigation: i,
278
- onImageClick: () => t?.(l)
404
+ preloadAdjacent: a,
405
+ onImageClick: () => t?.(u)
279
406
  })
280
407
  });
281
408
  }
282
- function D({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeight: u, className: f = "", style: p, renderCaption: m, showCounter: h, showNavigation: g }) {
283
- let _ = a([]), v = a(null), y = a(null), [x, D] = o(0), [O, k] = o({
409
+ function V({ images: e, onImageClick: n, layout: r = "grid", appearance: i = "framed", columns: u, rowHeight: f, className: p = "", style: m, renderCaption: h, showCounter: g, showNavigation: _, preloadAdjacent: y = !0 }) {
410
+ let b = a([]), x = a(null), S = a(null), [C, w] = o(0), [T, E] = o({
284
411
  width: 1024,
285
- rowHeight: C,
286
- gap: w
287
- }), A = i !== void 0 && Number.isFinite(i) && i >= 1 ? Math.floor(i) : S, j = e.length - 1, M = Math.min(x, Math.max(j, 0)), N = typeof u == "number" ? `${u}px` : u, P = {
288
- ...p,
289
- ...r === "grid" && i !== void 0 && Number.isFinite(i) && i >= 1 ? { gridTemplateColumns: `repeat(${A}, minmax(0, 1fr))` } : {},
290
- ...N ? { "--gallery-row-height": N } : {}
412
+ rowHeight: F,
413
+ gap: I
414
+ }), D = u !== void 0 && Number.isFinite(u) && u >= 1 ? Math.floor(u) : P, O = e.length - 1, k = Math.min(C, Math.max(O, 0)), A = typeof f == "number" ? `${f}px` : f, j = {
415
+ ...m,
416
+ ...r === "grid" && u !== void 0 && Number.isFinite(u) && u >= 1 ? { gridTemplateColumns: `repeat(${D}, minmax(0, 1fr))` } : {},
417
+ ...A ? { "--gallery-row-height": A } : {}
291
418
  };
292
- T(() => {
419
+ L(() => {
293
420
  if (r !== "justified") return;
294
- let e = v.current, t = y.current;
421
+ let e = x.current, t = S.current;
295
422
  if (!e || !t) return;
296
423
  let n = () => {
297
- let n = e.getBoundingClientRect().width || e.clientWidth || window.innerWidth || 1024, r = t.getBoundingClientRect(), i = Number.parseFloat(window.getComputedStyle(t).width), a = typeof u == "number" ? u : typeof u == "string" && /^\s*\d+(?:\.\d+)?px\s*$/.test(u) ? Number.parseFloat(u) : C;
298
- k((e) => {
424
+ let n = e.getBoundingClientRect(), r = window.getComputedStyle(e), i = (R(r.paddingLeft) ?? 0) + (R(r.paddingRight) ?? 0), a = (R(r.borderLeftWidth) ?? 0) + (R(r.borderRightWidth) ?? 0), o = [n.width - i - a, e.clientWidth - i].find(z) ?? (z(window.innerWidth) ? window.innerWidth : 1024), s = t.getBoundingClientRect(), c = Number.parseFloat(window.getComputedStyle(t).width), l = typeof f == "number" ? f : typeof f == "string" && /^\s*\d+(?:\.\d+)?px\s*$/.test(f) ? Number.parseFloat(f) : null, u = l !== null && z(l) ? l : F, d = z(s.height) ? s.height : u, p = [c, s.width].find((e) => Number.isFinite(e) && e >= 0) ?? I;
425
+ E((e) => {
299
426
  let t = {
300
- width: n,
301
- rowHeight: r.height || a,
302
- gap: Number.isFinite(i) ? i : r.width || w
427
+ width: o,
428
+ rowHeight: d,
429
+ gap: p
303
430
  };
304
431
  return e.width === t.width && e.rowHeight === t.rowHeight && e.gap === t.gap ? e : t;
305
432
  });
@@ -310,19 +437,20 @@ function D({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
310
437
  i?.disconnect(), window.removeEventListener("resize", n);
311
438
  };
312
439
  }, [
440
+ i,
313
441
  r,
314
- u,
315
- N
442
+ f,
443
+ A
316
444
  ]);
317
- let F = t((e) => {
318
- D(e), _.current[e]?.focus();
319
- }, []), I = t((t, n) => {
320
- let i = _.current[t];
445
+ let N = t((e) => {
446
+ w(e), b.current[e]?.focus();
447
+ }, []), V = t((t, n) => {
448
+ let i = b.current[t];
321
449
  if (!i) return null;
322
450
  let a = i.getBoundingClientRect();
323
451
  if (r === "mosaic" && i.classList.contains("react-pic-gallery__tile--mosaic-featured")) {
324
452
  let r = i.closest(".react-pic-gallery__mosaic-group"), o = a.left + a.width / 2, s = a.top + a.height / 2, c = null, l = Infinity;
325
- if (_.current.slice(0, e.length).forEach((e, i) => {
453
+ if (b.current.slice(0, e.length).forEach((e, i) => {
326
454
  if (!e || i === t || !r?.contains(e)) return;
327
455
  let a = e.getBoundingClientRect(), u = a.left + a.width / 2, d = a.top + a.height / 2, f = n === "up" ? s - d : d - s;
328
456
  if (f <= 0) return;
@@ -331,89 +459,95 @@ function D({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
331
459
  }), c !== null) return c;
332
460
  }
333
461
  let o = null, s = Infinity;
334
- return _.current.slice(0, e.length).forEach((e, r) => {
462
+ return b.current.slice(0, e.length).forEach((e, r) => {
335
463
  if (!e || r === t) return;
336
464
  let i = e.getBoundingClientRect(), c = n === "up" ? a.top - i.bottom : i.top - a.bottom;
337
465
  if (c < 0) return;
338
466
  let l = c + (i.right < a.left ? a.left - i.right : a.right < i.left ? i.left - a.right : 0);
339
467
  l < s && (s = l, o = r);
340
468
  }), o;
341
- }, [e.length, r]), L = t((e, t) => {
469
+ }, [e.length, r]), H = t((e, t) => {
342
470
  let n = null;
343
471
  if (e.key === "Home") n = 0;
344
- else if (e.key === "End") n = j;
472
+ else if (e.key === "End") n = O;
345
473
  else if (r === "grid") {
346
474
  let r = {
347
475
  ArrowRight: 1,
348
476
  ArrowLeft: -1,
349
- ArrowDown: A,
350
- ArrowUp: -A
477
+ ArrowDown: D,
478
+ ArrowUp: -D
351
479
  }[e.key];
352
480
  if (r !== void 0) {
353
481
  let e = t + r;
354
- e >= 0 && e <= j && (n = e);
482
+ e >= 0 && e <= O && (n = e);
355
483
  }
356
- } else if (e.key === "ArrowRight" && t < j) n = t + 1;
484
+ } else if (e.key === "ArrowRight" && t < O) n = t + 1;
357
485
  else if (e.key === "ArrowLeft" && t > 0) n = t - 1;
358
- else if (e.key === "ArrowDown") n = I(t, "down");
359
- else if (e.key === "ArrowUp") n = I(t, "up");
486
+ else if (e.key === "ArrowDown") n = V(t, "down");
487
+ else if (e.key === "ArrowUp") n = V(t, "up");
360
488
  else return;
361
- n !== null && n !== t && (e.preventDefault(), F(n));
489
+ n !== null && n !== t && (e.preventDefault(), N(n));
362
490
  }, [
363
- A,
364
- I,
365
- j,
491
+ D,
492
+ V,
493
+ O,
366
494
  r,
367
- F
368
- ]), R = (e, t, r = {}) => /* @__PURE__ */ c("button", {
369
- className: `react-pic-gallery__tile ${r.className ?? ""}`.trim(),
370
- type: "button",
371
- disabled: !n,
372
- tabIndex: t === M ? 0 : -1,
373
- ref: (e) => {
374
- _.current[t] = e;
375
- },
376
- style: r.style,
377
- onKeyDown: (e) => L(e, t),
378
- onClick: () => {
379
- D(t), n?.(t);
380
- },
381
- "aria-label": `Open ${e.alt}`,
382
- children: /* @__PURE__ */ c(d, {
383
- src: e.thumbnailSrc ?? e.src,
384
- alt: e.alt,
385
- objectFit: "cover",
386
- width: e.width,
387
- height: e.height
388
- })
389
- }, e.id ?? `${e.src}-${t}`), z = r === "justified" ? b(e, O.width, O.rowHeight, O.gap) : [], B = Array(e.length);
390
- return z.forEach((e) => {
495
+ N
496
+ ]), U = (e, t, r = {}) => {
497
+ let i = v(e);
498
+ return /* @__PURE__ */ c("button", {
499
+ className: `react-pic-gallery__tile ${r.className ?? ""}`.trim(),
500
+ type: "button",
501
+ disabled: !n,
502
+ tabIndex: t === k ? 0 : -1,
503
+ ref: (e) => {
504
+ b.current[t] = e;
505
+ },
506
+ style: r.style,
507
+ onKeyDown: (e) => H(e, t),
508
+ onClick: () => {
509
+ w(t), n?.(t);
510
+ },
511
+ "aria-label": `Open ${e.alt}`,
512
+ children: /* @__PURE__ */ c(d, {
513
+ sizes: i.sizes,
514
+ srcSet: i.srcSet,
515
+ src: i.src,
516
+ alt: e.alt,
517
+ objectFit: "cover",
518
+ width: e.width,
519
+ height: e.height
520
+ })
521
+ }, e.id ?? `${e.src}-${t}`);
522
+ }, W = r === "justified" ? M(e, T.width, T.rowHeight, T.gap) : [], G = Array(e.length);
523
+ return W.forEach((e) => {
391
524
  e.indices.forEach((t, n) => {
392
- B[t] = {
525
+ G[t] = {
393
526
  width: `${e.widths[n]}px`,
394
527
  height: `${e.height}px`
395
528
  };
396
529
  });
397
530
  }), /* @__PURE__ */ l("div", {
398
- ref: v,
399
- className: `react-pic-gallery__gallery react-pic-gallery__gallery--${r} ${r === "grid" ? "react-pic-gallery__grid" : ""} ${f}`.trim(),
400
- style: P,
531
+ ref: x,
532
+ className: `react-pic-gallery__gallery react-pic-gallery__gallery--${r} react-pic-gallery__gallery--${i} ${r === "grid" ? "react-pic-gallery__grid" : ""} ${p}`.trim(),
533
+ style: j,
401
534
  "data-testid": r === "grid" ? "react-pic-gallery-grid" : void 0,
402
535
  "data-layout": r,
403
536
  children: [
404
- r === "grid" && e.map((e, t) => R(e, t)),
405
- r === "carousel" && /* @__PURE__ */ c(E, {
537
+ r === "grid" && e.map((e, t) => U(e, t)),
538
+ r === "carousel" && /* @__PURE__ */ c(B, {
406
539
  images: e,
407
540
  onImageClick: n,
408
- renderCaption: m,
409
- showCounter: h,
410
- showNavigation: g
541
+ renderCaption: h,
542
+ showCounter: g,
543
+ showNavigation: _,
544
+ preloadAdjacent: y
411
545
  }),
412
- r === "justified" && /* @__PURE__ */ l(s, { children: [e.map((e, t) => R(e, t, {
546
+ r === "justified" && /* @__PURE__ */ l(s, { children: [e.map((e, t) => U(e, t, {
413
547
  className: "react-pic-gallery__tile--justified",
414
- style: B[t]
548
+ style: G[t]
415
549
  })), /* @__PURE__ */ c("span", {
416
- ref: y,
550
+ ref: S,
417
551
  className: "react-pic-gallery__row-height-probe",
418
552
  "aria-hidden": "true"
419
553
  })] }),
@@ -423,147 +557,151 @@ function D({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
423
557
  className: `react-pic-gallery__mosaic-group react-pic-gallery__mosaic-group--${a} react-pic-gallery__mosaic-group--count-${i.length}`,
424
558
  children: i.map((e, t) => {
425
559
  let n = r + t, a = i.length === 3 ? t === 0 ? "featured" : "supporting" : "simple";
426
- return R(e, n, { className: `react-pic-gallery__tile--mosaic react-pic-gallery__tile--mosaic-${a}` });
560
+ return U(e, n, { className: `react-pic-gallery__tile--mosaic react-pic-gallery__tile--mosaic-${a}` });
427
561
  })
428
562
  }, `mosaic-${r}`);
429
563
  })
430
564
  ]
431
565
  });
432
566
  }
433
- var O = e(D), k = /* @__PURE__ */ new Set(), A = null;
434
- function j(e) {
435
- if (!k.has(e)) {
436
- if (k.size === 0) {
567
+ var H = e(V), U = /* @__PURE__ */ new Set(), W = null;
568
+ function G(e) {
569
+ if (!U.has(e)) {
570
+ if (U.size === 0) {
437
571
  let e = document.body, t = window.innerWidth - document.documentElement.clientWidth, n = Number.parseFloat(window.getComputedStyle(e).paddingRight) || 0;
438
- A = {
572
+ W = {
439
573
  overflow: e.style.overflow,
440
574
  paddingRight: e.style.paddingRight
441
575
  }, e.style.overflow = "hidden", t > 0 && (e.style.paddingRight = `${n + t}px`);
442
576
  }
443
- k.add(e);
577
+ U.add(e);
444
578
  }
445
579
  }
446
- function M(e) {
447
- return k.delete(e), k.size > 0 || !A ? !1 : (document.body.style.overflow = A.overflow, document.body.style.paddingRight = A.paddingRight, A = null, !0);
580
+ function K(e) {
581
+ return U.delete(e), U.size > 0 || !W ? !1 : (document.body.style.overflow = W.overflow, document.body.style.paddingRight = W.paddingRight, W = null, !0);
448
582
  }
449
- function N(e) {
583
+ function q(e) {
450
584
  let t = e.trim();
451
585
  return t.endsWith("ms") ? Number.parseFloat(t) : t.endsWith("s") ? Number.parseFloat(t) * 1e3 : 0;
452
586
  }
453
- function P(e) {
587
+ function J(e) {
454
588
  let t = window.getComputedStyle(e), n = t.animationDuration.split(","), r = t.animationDelay.split(",");
455
- return Math.max(...n.map((e, t) => N(e) + N(r[t % r.length] ?? "0s")), 0);
589
+ return Math.max(...n.map((e, t) => q(e) + q(r[t % r.length] ?? "0s")), 0);
456
590
  }
457
- function F({ images: e, index: i, onIndexChange: s, renderActions: d, renderCaption: f, renderControls: p, showCounter: m = !0, showNavigation: h = !0, className: v }) {
458
- let y = a(null), b = a(s), S = a(null), C = a(null), w = a(null), T = a(!1), E = a({}), D = r(), [O, k] = o(!1), A = i !== null && i >= 0 && i < e.length ? i : null, N = A === null ? void 0 : e[A];
459
- b.current = s, w.current = A;
460
- let F = x({
591
+ function Y({ images: e, index: i, onIndexChange: s, renderActions: d, renderCaption: f, renderControls: p, showCounter: m = !0, showNavigation: h = !0, preloadAdjacent: _ = !0, className: v }) {
592
+ let y = a(null), b = a(s), x = a(null), S = a(null), C = a(null), w = a(!1), T = a({}), E = r(), [D, O] = o(!1), A = i !== null && i >= 0 && i < e.length ? i : null, j = A === null ? void 0 : e[A];
593
+ b.current = s, C.current = A;
594
+ let M = N({
461
595
  images: e,
462
596
  index: A,
463
597
  onIndexChange: s,
464
- disabled: O,
598
+ disabled: D,
465
599
  showNavigation: h && !p
466
- }), I = t(() => {
467
- S.current !== null && (window.clearTimeout(S.current), S.current = null), C.current = null;
468
- }, []), L = t(() => {
469
- let e = w.current;
470
- if (e !== null && C.current === null) {
600
+ }), P = t(() => {
601
+ x.current !== null && (window.clearTimeout(x.current), x.current = null), S.current = null;
602
+ }, []), F = t(() => {
603
+ let e = C.current;
604
+ if (e !== null && S.current === null) {
471
605
  if (typeof window < "u" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
472
606
  b.current(null);
473
607
  return;
474
608
  }
475
- C.current = { index: e }, k(!0);
609
+ S.current = { index: e }, O(!0);
476
610
  }
477
- }, []), R = t((e) => {
478
- e.preventDefault(), L();
479
- }, [L]), z = t(() => {
480
- let e = C.current;
611
+ }, []), I = t((e) => {
612
+ e.preventDefault(), F();
613
+ }, [F]), L = t(() => {
614
+ let e = S.current;
481
615
  if (e) {
482
- if (w.current !== e.index) {
483
- I(), k(!1);
616
+ if (C.current !== e.index) {
617
+ P(), O(!1);
484
618
  return;
485
619
  }
486
- I(), k(!1), b.current(null);
620
+ P(), O(!1), b.current(null);
487
621
  }
488
- }, [I]), B = t((e) => {
489
- e.target === e.currentTarget && e.animationName === "react-pic-gallery-fade-out" && z();
490
- }, [z]), V = t(() => {
491
- T.current || (I(), k(!1), w.current !== null && b.current(null));
492
- }, [I]);
622
+ }, [P]), R = t((e) => {
623
+ e.target === e.currentTarget && e.animationName === "react-pic-gallery-fade-out" && L();
624
+ }, [L]), z = t(() => {
625
+ w.current || (P(), O(!1), C.current !== null && b.current(null));
626
+ }, [P]);
493
627
  return n(() => {
494
- let e = C.current;
495
- e && A !== e.index && (I(), k(!1));
496
- }, [A, I]), n(() => {
497
- if (!O || !C.current || !y.current) return;
498
- let e = P(y.current);
499
- return S.current = window.setTimeout(z, e), () => {
500
- S.current !== null && (window.clearTimeout(S.current), S.current = null);
628
+ let e = S.current;
629
+ e && A !== e.index && (P(), O(!1));
630
+ }, [A, P]), n(() => {
631
+ if (!D || !S.current || !y.current) return;
632
+ let e = J(y.current);
633
+ return x.current = window.setTimeout(L, e), () => {
634
+ x.current !== null && (window.clearTimeout(x.current), x.current = null);
501
635
  };
502
- }, [z, O]), n(() => {
636
+ }, [L, D]), n(() => {
503
637
  if (A === null) return;
504
638
  let e = y.current;
505
639
  if (!e) return;
506
640
  let t = document.activeElement;
507
- return j(E.current), e.open || e.showModal(), () => {
508
- I(), T.current = !0, e.open && e.close(), T.current = !1, M(E.current) && t?.isConnected && t.focus();
641
+ return G(T.current), e.open || e.showModal(), () => {
642
+ P(), w.current = !0, e.open && e.close(), w.current = !1, K(T.current) && t?.isConnected && t.focus();
509
643
  };
510
- }, [A !== null, I]), A === null || !N || typeof document > "u" ? null : u(/* @__PURE__ */ l("dialog", {
644
+ }, [A !== null, P]), A === null || !j || typeof document > "u" ? null : u(/* @__PURE__ */ l("dialog", {
511
645
  ref: y,
512
- className: g("react-pic-gallery__lightbox", O ? "react-pic-gallery__lightbox--closing" : void 0, v),
513
- "aria-labelledby": D,
514
- onAnimationEnd: B,
515
- onClose: V,
646
+ className: g("react-pic-gallery__lightbox", D ? "react-pic-gallery__lightbox--closing" : void 0, v),
647
+ "aria-labelledby": E,
648
+ onAnimationEnd: R,
649
+ onClose: z,
516
650
  onMouseDown: (e) => {
517
- e.target === e.currentTarget && L();
651
+ e.target === e.currentTarget && F();
518
652
  },
519
- onCancel: R,
520
- onKeyDown: F.handleKeyDown,
653
+ onCancel: I,
654
+ onKeyDown: M.handleKeyDown,
521
655
  children: [/* @__PURE__ */ c("h2", {
522
656
  className: "react-pic-gallery__sr-only",
523
- id: D,
657
+ id: E,
524
658
  children: "Image viewer"
525
- }), /* @__PURE__ */ c(_, {
659
+ }), /* @__PURE__ */ c(k, {
526
660
  images: e,
527
661
  index: A,
528
- navigation: F,
662
+ navigation: M,
529
663
  renderActions: d,
530
664
  renderCaption: f,
531
665
  renderControls: p,
532
666
  showCounter: m,
533
667
  showNavigation: h && !p,
534
- close: L,
535
- onEmptyAreaClick: L
668
+ preloadAdjacent: _,
669
+ close: F,
670
+ onEmptyAreaClick: F
536
671
  })]
537
672
  }), document.body);
538
673
  }
539
674
  //#endregion
540
675
  //#region src/lib/PicGallery.tsx
541
- function I({ images: e, layout: t, columns: n, rowHeight: r, className: i, galleryClassName: a, style: s, renderActions: u, renderCaption: d, renderControls: f, showCounter: p, showNavigation: m }) {
542
- let [h, g] = o(null);
676
+ function X({ images: e, layout: t, appearance: n, columns: r, rowHeight: i, className: a, galleryClassName: s, style: u, renderActions: d, renderCaption: f, renderControls: p, showCounter: m, showNavigation: h, preloadAdjacent: g }) {
677
+ let [_, v] = o(null);
543
678
  return /* @__PURE__ */ l("div", {
544
- className: `react-pic-gallery ${i ?? ""}`.trim(),
545
- style: s,
546
- children: [/* @__PURE__ */ c(O, {
679
+ className: `react-pic-gallery ${a ?? ""}`.trim(),
680
+ style: u,
681
+ children: [/* @__PURE__ */ c(H, {
547
682
  images: e,
548
683
  layout: t,
549
- columns: n,
550
- rowHeight: r,
551
- className: a,
552
- onImageClick: g,
553
- renderCaption: d,
554
- showCounter: p,
555
- showNavigation: m
556
- }), /* @__PURE__ */ c(F, {
684
+ appearance: n,
685
+ columns: r,
686
+ rowHeight: i,
687
+ className: s,
688
+ onImageClick: v,
689
+ renderCaption: f,
690
+ showCounter: m,
691
+ showNavigation: h,
692
+ preloadAdjacent: g
693
+ }), /* @__PURE__ */ c(Y, {
557
694
  images: e,
558
- index: h,
559
- onIndexChange: g,
560
- renderActions: u,
561
- renderCaption: d,
562
- renderControls: f,
563
- showCounter: p,
564
- showNavigation: m
695
+ index: _,
696
+ onIndexChange: v,
697
+ renderActions: d,
698
+ renderCaption: f,
699
+ renderControls: p,
700
+ showCounter: m,
701
+ showNavigation: h,
702
+ preloadAdjacent: g
565
703
  })]
566
704
  });
567
705
  }
568
706
  //#endregion
569
- export { O as Gallery, F as Lightbox, I as PicGallery, I as default };
707
+ export { H as Gallery, Y as Lightbox, X as PicGallery, X as default };
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
- :where(.react-pic-gallery,.react-pic-gallery__gallery,.react-pic-gallery__carousel,.react-pic-gallery__lightbox){--gallery-gap:.75rem;--gallery-radius:1rem;--gallery-row-height:12rem;--gallery-carousel-height:24rem;--gallery-accent:#9ef01a;--gallery-overlay:#070b10f0;--gallery-control-size:2.75rem;--gallery-motion:.18s;color:#f8fafc;-webkit-user-select:none;user-select:none}.react-pic-gallery{width:100%;min-width:0}.react-pic-gallery__carousel{width:100%;height:var(--gallery-carousel-height,24rem);flex-direction:column;min-width:0;display:flex;position:relative}.react-pic-gallery__carousel:focus-visible{outline:none}.react-pic-gallery__carousel .react-pic-gallery__lightbox-content{width:100%;max-width:none;height:100%;max-height:none}.react-pic-gallery__carousel .react-pic-gallery__toolbar{z-index:1;pointer-events:none;gap:.5rem;min-height:0;position:absolute;top:.75rem;left:.75rem}.react-pic-gallery__carousel .react-pic-gallery__toolbar-actions{background:#070b108c;border-radius:999px;padding:.2rem .55rem}.react-pic-gallery__gallery{width:100%;min-width:0;position:relative}.react-pic-gallery__grid{gap:var(--gallery-gap,.75rem);grid-template-columns:repeat(3,minmax(0,1fr));width:100%;display:grid}.react-pic-gallery__gallery--justified{align-content:flex-start;gap:var(--gallery-gap,.75rem);flex-wrap:wrap;display:flex}.react-pic-gallery__gallery--justified>.react-pic-gallery__tile--justified{flex:none;height:auto}.react-pic-gallery__row-height-probe{width:var(--gallery-gap,.75rem);height:var(--gallery-row-height,12rem);visibility:hidden;pointer-events:none;position:absolute;overflow:hidden}.react-pic-gallery__gallery--mosaic{gap:var(--gallery-gap,.75rem);display:grid}.react-pic-gallery__mosaic-group{grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);grid-template-rows:repeat(2, minmax(0, var(--gallery-row-height,12rem)));gap:var(--gallery-gap,.75rem);min-width:0;display:grid}.react-pic-gallery__mosaic-group--top{grid-template-columns:repeat(2,minmax(0,1fr))}.react-pic-gallery__mosaic-group .react-pic-gallery__tile--mosaic{min-width:0;height:auto}.react-pic-gallery__mosaic-group--left .react-pic-gallery__tile--mosaic-featured{grid-area:1/1/span 2}.react-pic-gallery__mosaic-group--top .react-pic-gallery__tile--mosaic-featured{grid-area:1/1/auto/-1}.react-pic-gallery__mosaic-group--count-2{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:minmax(0, var(--gallery-row-height,12rem))}.react-pic-gallery__mosaic-group--count-1{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0, var(--gallery-row-height,12rem))}.react-pic-gallery__tile{min-width:0;height:var(--gallery-row-height,12rem);cursor:pointer;border-radius:var(--gallery-radius,1rem);isolation:isolate;background:0 0;border:0;padding:0;position:relative;overflow:hidden}.react-pic-gallery__tile:after{content:"";pointer-events:none;border-radius:inherit;transition:border-color var(--gallery-motion,.18s) ease;border:2px solid #0000;position:absolute;inset:0}.react-pic-gallery__tile:hover .react-pic-gallery__image-element,.react-pic-gallery__tile:focus-visible .react-pic-gallery__image-element{transform:scale(1.04)}.react-pic-gallery__tile:focus-visible{outline:3px solid var(--gallery-accent,#9ef01a);outline-offset:3px}.react-pic-gallery__tile:focus-visible:after{border-color:var(--gallery-accent,#9ef01a)}.react-pic-gallery__tile:disabled{cursor:default}.react-pic-gallery__image{border-radius:inherit;width:100%;height:100%;display:block;position:relative;overflow:hidden}.react-pic-gallery__image-element{width:100%;height:100%;transition:transform var(--gallery-motion,.18s) ease, opacity var(--gallery-motion,.18s) ease;display:block}.react-pic-gallery__image-loader,.react-pic-gallery__image-error{background:linear-gradient(135deg,#e2e8f0,#cbd5e1);place-items:center;display:grid;position:absolute;inset:0}.react-pic-gallery__image-loader:after{content:"";border:2px solid #0f172a29;border-top-color:var(--gallery-accent,#9ef01a);border-radius:50%;width:1.5rem;height:1.5rem;animation:.7s linear infinite react-pic-gallery-spin}.react-pic-gallery__image-error{color:#475569;text-align:center;background:#e2e8f0e6;padding:1rem;font-size:.875rem}:where(dialog.react-pic-gallery__lightbox){z-index:1000;box-sizing:border-box;width:100%;max-width:none;height:100dvh;max-height:none;padding:max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));color:#f8fafc;background:var(--gallery-overlay);animation:react-pic-gallery-fade-in var(--gallery-motion) ease both;border:0;place-items:center;margin:0;display:grid;position:fixed;inset:0;overflow:hidden}dialog.react-pic-gallery__lightbox:not([open]){display:none}dialog.react-pic-gallery__lightbox::backdrop{background:0 0}.react-pic-gallery__lightbox--closing{animation:react-pic-gallery-fade-out var(--gallery-motion) ease both}.react-pic-gallery__lightbox-content{box-sizing:border-box;width:min(90rem,100vw - 2rem);min-width:0;max-width:100%;height:min(54rem,100dvh - 2rem);min-height:0;max-height:100%;animation:react-pic-gallery-rise-in var(--gallery-motion) ease both;flex-direction:column;display:flex}.react-pic-gallery__lightbox--closing .react-pic-gallery__lightbox-content{animation:react-pic-gallery-rise-out var(--gallery-motion) ease both}.react-pic-gallery__toolbar,.react-pic-gallery__custom-controls{flex-wrap:wrap;flex:none;justify-content:space-between;align-items:center;gap:1rem;min-height:3.5rem;display:flex}.react-pic-gallery__toolbar-actions{color:#cbd5e1;align-items:center;gap:.75rem;min-width:0;font-size:.875rem;display:flex}.react-pic-gallery__control{width:var(--gallery-control-size);height:var(--gallery-control-size);color:#f8fafc;cursor:pointer;transition:background var(--gallery-motion) ease, color var(--gallery-motion) ease, transform var(--gallery-motion) ease;background:0 0;border:0;border-radius:.7rem;flex:none;place-items:center;padding:0;font-size:1.4rem;line-height:1;display:inline-grid}.react-pic-gallery__control:hover{background:#f8fafc24}.react-pic-gallery__control:focus-visible{outline:3px solid var(--gallery-accent);outline-offset:3px}.react-pic-gallery__control svg{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8px;width:1.25rem;height:1.25rem}.react-pic-gallery__viewport{touch-action:pan-y pinch-zoom;flex:1 1 0;place-items:center;min-width:0;min-height:0;padding:.75rem;display:grid;position:relative;overflow:hidden}.react-pic-gallery__viewport--has-navigation{padding-inline:3.5rem}.react-pic-gallery__viewport--has-navigation .react-pic-gallery__lightbox-image{inset-inline:3.5rem}.react-pic-gallery__lightbox-image{pointer-events:none;width:auto;min-width:0;max-width:82rem;height:auto;min-height:0;max-height:none;animation:react-pic-gallery-image-in var(--gallery-motion) ease both;background:0 0;border-radius:0;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr);place-items:center;margin-inline:auto;display:grid;position:absolute;inset:.75rem;overflow:visible}.react-pic-gallery__lightbox-image .react-pic-gallery__image-element{pointer-events:auto;touch-action:inherit;border-radius:var(--gallery-radius);background:0 0;width:auto;max-width:100%;height:auto;max-height:100%;object-fit:contain!important}.react-pic-gallery__lightbox-image .react-pic-gallery__image-loader{pointer-events:none;background:0 0}.react-pic-gallery__lightbox-image .react-pic-gallery__image-error{pointer-events:none}.react-pic-gallery__lightbox-image--swipe-next{animation-name:react-pic-gallery-slide-in-from-right}.react-pic-gallery__lightbox-image--swipe-previous{animation-name:react-pic-gallery-slide-in-from-left}.react-pic-gallery__control--previous,.react-pic-gallery__control--next{z-index:1;position:absolute;top:50%;transform:translateY(-50%)}.react-pic-gallery__control--previous{left:0}.react-pic-gallery__control--next{right:0}.react-pic-gallery__caption{color:#cbd5e1;text-align:center;flex:none;max-height:8rem;padding:.75rem 0 0;font-size:.95rem;line-height:1.4;overflow:hidden}.react-pic-gallery__sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;padding:0;position:absolute;overflow:hidden}@keyframes react-pic-gallery-spin{to{transform:rotate(360deg)}}@keyframes react-pic-gallery-fade-in{0%{opacity:0}to{opacity:1}}@keyframes react-pic-gallery-fade-out{0%{opacity:1}to{opacity:0}}@keyframes react-pic-gallery-rise-in{0%{opacity:0;transform:translateY(.5rem)scale(.99)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes react-pic-gallery-rise-out{0%{opacity:1;transform:translateY(0)scale(1)}to{opacity:0;transform:translateY(.5rem)scale(.99)}}@keyframes react-pic-gallery-image-in{0%{opacity:0;transform:scale(.985)}to{opacity:1;transform:scale(1)}}@keyframes react-pic-gallery-slide-in-from-right{0%{opacity:0;transform:translate(18%)}to{opacity:1;transform:translate(0)}}@keyframes react-pic-gallery-slide-in-from-left{0%{opacity:0;transform:translate(-18%)}to{opacity:1;transform:translate(0)}}@media (width<=600px){:where(.react-pic-gallery,.react-pic-gallery__lightbox){--gallery-gap:.5rem;--gallery-radius:.75rem;--gallery-row-height:8rem;--gallery-carousel-height:14rem}:where(dialog.react-pic-gallery__lightbox){padding:max(.375rem, env(safe-area-inset-top)) max(.375rem, env(safe-area-inset-right)) max(.375rem, env(safe-area-inset-bottom)) max(.375rem, env(safe-area-inset-left))}.react-pic-gallery__mosaic-group--count-3{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:minmax(8rem, var(--gallery-row-height,12rem)) minmax(5rem, .65fr)}.react-pic-gallery__mosaic-group--count-3 .react-pic-gallery__tile--mosaic-featured{grid-area:1/1/auto/-1}.react-pic-gallery__lightbox-content{width:min(90rem,100vw - .75rem);height:min(54rem,100dvh - .75rem)}.react-pic-gallery__toolbar,.react-pic-gallery__custom-controls{min-height:3rem}.react-pic-gallery__viewport{padding:.25rem}.react-pic-gallery__lightbox-image{inset:.25rem}.react-pic-gallery__viewport--has-navigation .react-pic-gallery__lightbox-image{inset-inline:.25rem}.react-pic-gallery__control--previous,.react-pic-gallery__control--next{display:none}}@media (height<=600px){:where(dialog.react-pic-gallery__lightbox){padding:max(.25rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(.25rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left))}.react-pic-gallery__lightbox-content{height:min(54rem,100dvh - .5rem)}.react-pic-gallery__toolbar,.react-pic-gallery__custom-controls{min-height:2.25rem}.react-pic-gallery__caption{max-height:4rem;padding:.35rem 0 0;font-size:.85rem}.react-pic-gallery__viewport{padding:.25rem}.react-pic-gallery__lightbox-image{inset:.25rem}}@media (prefers-reduced-motion:reduce){:where(.react-pic-gallery,.react-pic-gallery__lightbox){--gallery-motion:0s}.react-pic-gallery__image-loader:after{animation:none}}
1
+ :where(.react-pic-gallery,.react-pic-gallery__lightbox,.react-pic-gallery__gallery:not(.react-pic-gallery .react-pic-gallery__gallery)){--gallery-gap:.75rem;--gallery-radius:1rem;--gallery-row-height:12rem;--gallery-carousel-height:24rem;--gallery-accent:#9ef01a;--gallery-overlay:#070b10f0;--gallery-control-size:2.75rem;--gallery-motion:.18s;--gallery-background:#111a24;--gallery-padding:.65rem;--gallery-border:1px solid #94a3b82e;--gallery-frame-radius:1.1rem;color:#f8fafc;-webkit-user-select:none;user-select:none}.react-pic-gallery{width:100%;min-width:0}.react-pic-gallery__carousel{width:100%;height:var(--gallery-carousel-height,24rem);flex-direction:column;min-width:0;display:flex;position:relative}.react-pic-gallery__carousel:focus-visible{outline:none}.react-pic-gallery__carousel .react-pic-gallery__lightbox-content{width:100%;max-width:none;height:100%;max-height:none}.react-pic-gallery__carousel .react-pic-gallery__toolbar{z-index:1;pointer-events:none;gap:.5rem;min-height:0;position:absolute;top:.75rem;left:.75rem}.react-pic-gallery__carousel .react-pic-gallery__toolbar-actions{background:#070b108c;border-radius:999px;padding:.2rem .55rem}.react-pic-gallery__gallery{width:100%;min-width:0;position:relative}.react-pic-gallery__gallery--framed{box-sizing:border-box;padding:var(--gallery-padding,.65rem);border:var(--gallery-border,1px solid #94a3b82e);border-radius:var(--gallery-frame-radius,1.1rem);background:var(--gallery-background,#111a24)}.react-pic-gallery__gallery--bare{box-sizing:border-box;background:0 0;border:0;border-radius:0;padding:0}.react-pic-gallery__grid{gap:var(--gallery-gap,.75rem);grid-template-columns:repeat(3,minmax(0,1fr));width:100%;display:grid}.react-pic-gallery__gallery--justified{align-content:flex-start;gap:var(--gallery-gap,.75rem);flex-wrap:wrap;display:flex}.react-pic-gallery__gallery--justified>.react-pic-gallery__tile--justified{flex:none;height:auto}.react-pic-gallery__row-height-probe{width:var(--gallery-gap,.75rem);height:var(--gallery-row-height,12rem);visibility:hidden;pointer-events:none;position:absolute;overflow:hidden}.react-pic-gallery__gallery--mosaic{gap:var(--gallery-gap,.75rem);display:grid}.react-pic-gallery__mosaic-group{grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);grid-template-rows:repeat(2, minmax(0, var(--gallery-row-height,12rem)));gap:var(--gallery-gap,.75rem);min-width:0;display:grid}.react-pic-gallery__mosaic-group--top{grid-template-columns:repeat(2,minmax(0,1fr))}.react-pic-gallery__mosaic-group .react-pic-gallery__tile--mosaic{min-width:0;height:auto}.react-pic-gallery__mosaic-group--left .react-pic-gallery__tile--mosaic-featured{grid-area:1/1/span 2}.react-pic-gallery__mosaic-group--top .react-pic-gallery__tile--mosaic-featured{grid-area:1/1/auto/-1}.react-pic-gallery__mosaic-group--count-2{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:minmax(0, var(--gallery-row-height,12rem))}.react-pic-gallery__mosaic-group--count-1{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0, var(--gallery-row-height,12rem))}.react-pic-gallery__tile{min-width:0;height:var(--gallery-row-height,12rem);cursor:pointer;border-radius:var(--gallery-radius,1rem);isolation:isolate;background:0 0;border:0;padding:0;position:relative;overflow:hidden}.react-pic-gallery__tile:after{content:"";pointer-events:none;border-radius:inherit;transition:border-color var(--gallery-motion,.18s) ease;border:2px solid #0000;position:absolute;inset:0}.react-pic-gallery__tile:hover .react-pic-gallery__image-element,.react-pic-gallery__tile:focus-visible .react-pic-gallery__image-element{transform:scale(1.04)}.react-pic-gallery__tile:focus-visible{outline:3px solid var(--gallery-accent,#9ef01a);outline-offset:3px}.react-pic-gallery__tile:focus-visible:after{border-color:var(--gallery-accent,#9ef01a)}.react-pic-gallery__tile:disabled{cursor:default}.react-pic-gallery__image{border-radius:inherit;width:100%;height:100%;display:block;position:relative;overflow:hidden}.react-pic-gallery__image-element{width:100%;height:100%;transition:transform var(--gallery-motion,.18s) ease, opacity var(--gallery-motion,.18s) ease;display:block}.react-pic-gallery__image-loader,.react-pic-gallery__image-error{background:linear-gradient(135deg,#e2e8f0,#cbd5e1);place-items:center;display:grid;position:absolute;inset:0}.react-pic-gallery__image-loader:after{content:"";border:2px solid #0f172a29;border-top-color:var(--gallery-accent,#9ef01a);border-radius:50%;width:1.5rem;height:1.5rem;animation:.7s linear infinite react-pic-gallery-spin}.react-pic-gallery__image-error{color:#475569;text-align:center;background:#e2e8f0e6;padding:1rem;font-size:.875rem}:where(dialog.react-pic-gallery__lightbox){z-index:1000;box-sizing:border-box;width:100%;max-width:none;height:100dvh;max-height:none;padding:max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));color:#f8fafc;background:var(--gallery-overlay);animation:react-pic-gallery-fade-in var(--gallery-motion) ease both;border:0;place-items:center;margin:0;display:grid;position:fixed;inset:0;overflow:hidden}dialog.react-pic-gallery__lightbox:not([open]){display:none}dialog.react-pic-gallery__lightbox::backdrop{background:0 0}.react-pic-gallery__lightbox--closing{animation:react-pic-gallery-fade-out var(--gallery-motion) ease both}.react-pic-gallery__lightbox-content{box-sizing:border-box;width:min(90rem,100vw - 2rem);min-width:0;max-width:100%;height:min(54rem,100dvh - 2rem);min-height:0;max-height:100%;animation:react-pic-gallery-rise-in var(--gallery-motion) ease both;flex-direction:column;display:flex}.react-pic-gallery__lightbox--closing .react-pic-gallery__lightbox-content{animation:react-pic-gallery-rise-out var(--gallery-motion) ease both}.react-pic-gallery__toolbar,.react-pic-gallery__custom-controls{flex-wrap:wrap;flex:none;justify-content:space-between;align-items:center;gap:1rem;min-height:3.5rem;display:flex}.react-pic-gallery__toolbar-actions{color:#cbd5e1;align-items:center;gap:.75rem;min-width:0;font-size:.875rem;display:flex}.react-pic-gallery__control{width:var(--gallery-control-size);height:var(--gallery-control-size);color:#f8fafc;cursor:pointer;transition:background var(--gallery-motion) ease, color var(--gallery-motion) ease, transform var(--gallery-motion) ease;background:0 0;border:0;border-radius:.7rem;flex:none;place-items:center;padding:0;font-size:1.4rem;line-height:1;display:inline-grid}.react-pic-gallery__control:hover{background:#f8fafc24}.react-pic-gallery__control:focus-visible{outline:3px solid var(--gallery-accent);outline-offset:3px}.react-pic-gallery__control svg{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8px;width:1.25rem;height:1.25rem}.react-pic-gallery__viewport{touch-action:pan-y pinch-zoom;flex:1 1 0;place-items:center;min-width:0;min-height:0;padding:.75rem;display:grid;position:relative;overflow:hidden}.react-pic-gallery__viewport--has-navigation{padding-inline:3.5rem}.react-pic-gallery__viewport--has-navigation .react-pic-gallery__lightbox-image{inset-inline:3.5rem}.react-pic-gallery__lightbox-image{pointer-events:none;width:auto;min-width:0;max-width:82rem;height:auto;min-height:0;max-height:none;animation:react-pic-gallery-image-in var(--gallery-motion) ease both;background:0 0;border-radius:0;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr);place-items:center;margin-inline:auto;display:grid;position:absolute;inset:.75rem;overflow:visible}.react-pic-gallery__lightbox-image .react-pic-gallery__image-element{pointer-events:auto;touch-action:inherit;border-radius:var(--gallery-radius);background:0 0;width:auto;max-width:100%;height:auto;max-height:100%;object-fit:contain!important}.react-pic-gallery__lightbox-image .react-pic-gallery__image-loader{pointer-events:none;background:0 0}.react-pic-gallery__lightbox-image .react-pic-gallery__image-error{pointer-events:none}.react-pic-gallery__lightbox-image--swipe-next{animation-name:react-pic-gallery-slide-in-from-right}.react-pic-gallery__lightbox-image--swipe-previous{animation-name:react-pic-gallery-slide-in-from-left}.react-pic-gallery__control--previous,.react-pic-gallery__control--next{z-index:1;position:absolute;top:50%;transform:translateY(-50%)}.react-pic-gallery__control--previous{left:0}.react-pic-gallery__control--next{right:0}.react-pic-gallery__caption{color:#cbd5e1;text-align:center;flex:none;max-height:8rem;padding:.75rem 0 0;font-size:.95rem;line-height:1.4;overflow:hidden}.react-pic-gallery__sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;padding:0;position:absolute;overflow:hidden}@keyframes react-pic-gallery-spin{to{transform:rotate(360deg)}}@keyframes react-pic-gallery-fade-in{0%{opacity:0}to{opacity:1}}@keyframes react-pic-gallery-fade-out{0%{opacity:1}to{opacity:0}}@keyframes react-pic-gallery-rise-in{0%{opacity:0;transform:translateY(.5rem)scale(.99)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes react-pic-gallery-rise-out{0%{opacity:1;transform:translateY(0)scale(1)}to{opacity:0;transform:translateY(.5rem)scale(.99)}}@keyframes react-pic-gallery-image-in{0%{opacity:0;transform:scale(.985)}to{opacity:1;transform:scale(1)}}@keyframes react-pic-gallery-slide-in-from-right{0%{opacity:0;transform:translate(18%)}to{opacity:1;transform:translate(0)}}@keyframes react-pic-gallery-slide-in-from-left{0%{opacity:0;transform:translate(-18%)}to{opacity:1;transform:translate(0)}}@media (width<=600px){:where(.react-pic-gallery,.react-pic-gallery__lightbox,.react-pic-gallery__gallery:not(.react-pic-gallery .react-pic-gallery__gallery)){--gallery-gap:.5rem;--gallery-radius:.75rem;--gallery-row-height:8rem;--gallery-carousel-height:14rem}:where(dialog.react-pic-gallery__lightbox){padding:max(.375rem, env(safe-area-inset-top)) max(.375rem, env(safe-area-inset-right)) max(.375rem, env(safe-area-inset-bottom)) max(.375rem, env(safe-area-inset-left))}.react-pic-gallery__mosaic-group--count-3{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:minmax(8rem, var(--gallery-row-height,12rem)) minmax(5rem, .65fr)}.react-pic-gallery__mosaic-group--count-3 .react-pic-gallery__tile--mosaic-featured{grid-area:1/1/auto/-1}.react-pic-gallery__lightbox-content{width:min(90rem,100vw - .75rem);height:min(54rem,100dvh - .75rem)}.react-pic-gallery__toolbar,.react-pic-gallery__custom-controls{min-height:3rem}.react-pic-gallery__viewport{padding:.25rem}.react-pic-gallery__lightbox-image{inset:.25rem}.react-pic-gallery__viewport--has-navigation .react-pic-gallery__lightbox-image{inset-inline:.25rem}.react-pic-gallery__control--previous,.react-pic-gallery__control--next{display:none}}@media (height<=600px){:where(dialog.react-pic-gallery__lightbox){padding:max(.25rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(.25rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left))}.react-pic-gallery__lightbox-content{height:min(54rem,100dvh - .5rem)}.react-pic-gallery__toolbar,.react-pic-gallery__custom-controls{min-height:2.25rem}.react-pic-gallery__caption{max-height:4rem;padding:.35rem 0 0;font-size:.85rem}.react-pic-gallery__viewport{padding:.25rem}.react-pic-gallery__lightbox-image{inset:.25rem}}@media (prefers-reduced-motion:reduce){:where(.react-pic-gallery,.react-pic-gallery__lightbox,.react-pic-gallery__gallery:not(.react-pic-gallery .react-pic-gallery__gallery)){--gallery-motion:0s}.react-pic-gallery__image-loader:after{animation:none}}
2
2
  /*$vite$:1*/
package/dist/types.d.ts CHANGED
@@ -2,13 +2,18 @@ import { CSSProperties, ReactNode } from 'react';
2
2
  export type GalleryImage = {
3
3
  id?: string;
4
4
  src: string;
5
+ srcSet?: string;
6
+ sizes?: string;
5
7
  thumbnailSrc?: string;
8
+ thumbnailSrcSet?: string;
9
+ thumbnailSizes?: string;
6
10
  alt: string;
7
11
  caption?: ReactNode;
8
12
  width?: number;
9
13
  height?: number;
10
14
  };
11
15
  export type GalleryLayout = 'grid' | 'justified' | 'mosaic' | 'carousel';
16
+ export type GalleryAppearance = 'framed' | 'bare';
12
17
  export type LightboxContext<T extends GalleryImage = GalleryImage> = {
13
18
  image: T;
14
19
  index: number;
@@ -24,11 +29,13 @@ export type GalleryProps<T extends GalleryImage = GalleryImage> = {
24
29
  images: readonly T[];
25
30
  onImageClick?: (index: number) => void;
26
31
  layout?: GalleryLayout;
32
+ appearance?: GalleryAppearance;
27
33
  columns?: number;
28
34
  rowHeight?: CSSProperties['height'];
29
35
  renderCaption?: LightboxRenderer<T>;
30
36
  showCounter?: boolean;
31
37
  showNavigation?: boolean;
38
+ preloadAdjacent?: boolean;
32
39
  className?: string;
33
40
  style?: CSSProperties;
34
41
  };
@@ -41,11 +48,13 @@ export type LightboxProps<T extends GalleryImage = GalleryImage> = {
41
48
  renderControls?: LightboxRenderer<T>;
42
49
  showCounter?: boolean;
43
50
  showNavigation?: boolean;
51
+ preloadAdjacent?: boolean;
44
52
  className?: string;
45
53
  };
46
54
  export type PicGalleryProps<T extends GalleryImage = GalleryImage> = {
47
55
  images: readonly T[];
48
56
  layout?: GalleryLayout;
57
+ appearance?: GalleryAppearance;
49
58
  columns?: number;
50
59
  rowHeight?: CSSProperties['height'];
51
60
  className?: string;
@@ -56,4 +65,5 @@ export type PicGalleryProps<T extends GalleryImage = GalleryImage> = {
56
65
  renderControls?: LightboxRenderer<T>;
57
66
  showCounter?: boolean;
58
67
  showNavigation?: boolean;
68
+ preloadAdjacent?: boolean;
59
69
  };
@@ -0,0 +1,4 @@
1
+ import { GalleryImage } from './types';
2
+ export declare function useAdjacentImagePreload<T extends GalleryImage>(images: readonly T[], index: number, enabled: boolean): void;
3
+ /** Internal reset hook for isolated unit tests. */
4
+ export declare function resetAdjacentImagePreloadCache(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-pic-gallery",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "Small, accessible React image gallery and lightbox.",
5
5
  "author": "marcelrsoub",
6
6
  "license": "MIT",
@@ -32,6 +32,9 @@
32
32
  "preview": "npm run preview:docs",
33
33
  "test": "vitest run",
34
34
  "test:watch": "vitest",
35
+ "test:visual": "node scripts/visual-regression.mjs test",
36
+ "test:visual:update": "node scripts/visual-regression.mjs update",
37
+ "test:visual:cross": "node scripts/visual-regression.mjs cross",
35
38
  "build:demo": "npm run build:docs",
36
39
  "dev:docs": "astro dev --config docs-site/astro.config.mjs",
37
40
  "build:docs": "astro build --config docs-site/astro.config.mjs",
@@ -51,6 +54,7 @@
51
54
  "astro": "^7.3.3",
52
55
  "gh-pages": "^6.3.0",
53
56
  "jsdom": "^30.1.0",
57
+ "playwright-core": "^1.63.0",
54
58
  "react": "^19.3.0",
55
59
  "react-dom": "^19.3.0",
56
60
  "typescript": "^6.0.3",