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 +71 -15
- package/dist/Carousel.d.ts +1 -0
- package/dist/Gallery.d.ts +1 -1
- package/dist/Image.d.ts +3 -1
- package/dist/Lightbox.d.ts +1 -1
- package/dist/PicGallery.d.ts +1 -1
- package/dist/imageSources.d.ts +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/react-pic-gallery.js +349 -211
- package/dist/styles.css +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/useAdjacentImagePreload.d.ts +4 -0
- package/package.json +5 -1
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
|
[](https://www.npmjs.com/package/react-pic-gallery)
|
|
6
|
-
[](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 **
|
|
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; ~
|
|
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
|
-
- **
|
|
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/
|
|
47
|
-
|
|
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. `
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/dist/Carousel.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/Lightbox.d.ts
CHANGED
|
@@ -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;
|
package/dist/PicGallery.d.ts
CHANGED
|
@@ -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,
|
|
6
|
-
let
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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:
|
|
22
|
-
className: `react-pic-gallery__image-element ${
|
|
36
|
+
alt: i,
|
|
37
|
+
className: `react-pic-gallery__image-element ${u}`.trim(),
|
|
23
38
|
style: {
|
|
24
|
-
objectFit:
|
|
25
|
-
...
|
|
39
|
+
objectFit: g,
|
|
40
|
+
...f
|
|
26
41
|
},
|
|
27
|
-
loading:
|
|
42
|
+
loading: h ? "eager" : "lazy",
|
|
28
43
|
decoding: "async",
|
|
29
|
-
width:
|
|
30
|
-
height:
|
|
31
|
-
onLoad: () =>
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
98
|
+
//#region src/lib/imageSources.ts
|
|
78
99
|
function _(e) {
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
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",
|
|
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(
|
|
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?.(
|
|
117
|
-
}),
|
|
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",
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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:
|
|
150
|
-
f && t.length > 1 && p(e.clientX, e.currentTarget.parentElement) !== null ||
|
|
264
|
+
onClick: x ? (e) => {
|
|
265
|
+
f && t.length > 1 && p(e.clientX, e.currentTarget.parentElement) !== null || x();
|
|
151
266
|
} : void 0
|
|
152
|
-
},
|
|
153
|
-
|
|
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 ||
|
|
281
|
+
(a || C.caption) && /* @__PURE__ */ c("div", {
|
|
163
282
|
className: "react-pic-gallery__caption",
|
|
164
|
-
children: a ? a(
|
|
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
|
|
172
|
-
function
|
|
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 :
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
255
|
-
function
|
|
256
|
-
let
|
|
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 ?
|
|
384
|
+
index: e.length > 0 ? u : null,
|
|
259
385
|
onIndexChange: (e) => {
|
|
260
|
-
e !== null &&
|
|
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:
|
|
271
|
-
children: /* @__PURE__ */ c(
|
|
396
|
+
onKeyDown: d.handleKeyDown,
|
|
397
|
+
children: /* @__PURE__ */ c(k, {
|
|
272
398
|
images: e,
|
|
273
|
-
index:
|
|
274
|
-
navigation:
|
|
399
|
+
index: u,
|
|
400
|
+
navigation: d,
|
|
275
401
|
renderCaption: n,
|
|
276
402
|
showCounter: r,
|
|
277
403
|
showNavigation: i,
|
|
278
|
-
|
|
404
|
+
preloadAdjacent: a,
|
|
405
|
+
onImageClick: () => t?.(u)
|
|
279
406
|
})
|
|
280
407
|
});
|
|
281
408
|
}
|
|
282
|
-
function
|
|
283
|
-
let
|
|
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:
|
|
286
|
-
gap:
|
|
287
|
-
}),
|
|
288
|
-
...
|
|
289
|
-
...r === "grid" &&
|
|
290
|
-
...
|
|
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
|
-
|
|
419
|
+
L(() => {
|
|
293
420
|
if (r !== "justified") return;
|
|
294
|
-
let e =
|
|
421
|
+
let e = x.current, t = S.current;
|
|
295
422
|
if (!e || !t) return;
|
|
296
423
|
let n = () => {
|
|
297
|
-
let n = e.getBoundingClientRect().width
|
|
298
|
-
|
|
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:
|
|
301
|
-
rowHeight:
|
|
302
|
-
gap:
|
|
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
|
-
|
|
315
|
-
|
|
442
|
+
f,
|
|
443
|
+
A
|
|
316
444
|
]);
|
|
317
|
-
let
|
|
318
|
-
|
|
319
|
-
}, []),
|
|
320
|
-
let i =
|
|
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 (
|
|
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
|
|
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]),
|
|
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 =
|
|
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:
|
|
350
|
-
ArrowUp: -
|
|
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 <=
|
|
482
|
+
e >= 0 && e <= O && (n = e);
|
|
355
483
|
}
|
|
356
|
-
} else if (e.key === "ArrowRight" && t <
|
|
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 =
|
|
359
|
-
else if (e.key === "ArrowUp") n =
|
|
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(),
|
|
489
|
+
n !== null && n !== t && (e.preventDefault(), N(n));
|
|
362
490
|
}, [
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
491
|
+
D,
|
|
492
|
+
V,
|
|
493
|
+
O,
|
|
366
494
|
r,
|
|
367
|
-
|
|
368
|
-
]),
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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:
|
|
399
|
-
className: `react-pic-gallery__gallery react-pic-gallery__gallery--${r} ${r === "grid" ? "react-pic-gallery__grid" : ""} ${
|
|
400
|
-
style:
|
|
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) =>
|
|
405
|
-
r === "carousel" && /* @__PURE__ */ c(
|
|
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:
|
|
409
|
-
showCounter:
|
|
410
|
-
showNavigation:
|
|
541
|
+
renderCaption: h,
|
|
542
|
+
showCounter: g,
|
|
543
|
+
showNavigation: _,
|
|
544
|
+
preloadAdjacent: y
|
|
411
545
|
}),
|
|
412
|
-
r === "justified" && /* @__PURE__ */ l(s, { children: [e.map((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:
|
|
548
|
+
style: G[t]
|
|
415
549
|
})), /* @__PURE__ */ c("span", {
|
|
416
|
-
ref:
|
|
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
|
|
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
|
|
434
|
-
function
|
|
435
|
-
if (!
|
|
436
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
577
|
+
U.add(e);
|
|
444
578
|
}
|
|
445
579
|
}
|
|
446
|
-
function
|
|
447
|
-
return
|
|
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
|
|
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
|
|
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) =>
|
|
589
|
+
return Math.max(...n.map((e, t) => q(e) + q(r[t % r.length] ?? "0s")), 0);
|
|
456
590
|
}
|
|
457
|
-
function
|
|
458
|
-
let y = a(null), b = a(s),
|
|
459
|
-
b.current = s,
|
|
460
|
-
let
|
|
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:
|
|
598
|
+
disabled: D,
|
|
465
599
|
showNavigation: h && !p
|
|
466
|
-
}),
|
|
467
|
-
|
|
468
|
-
}, []),
|
|
469
|
-
let e =
|
|
470
|
-
if (e !== 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
|
-
|
|
609
|
+
S.current = { index: e }, O(!0);
|
|
476
610
|
}
|
|
477
|
-
}, []),
|
|
478
|
-
e.preventDefault(),
|
|
479
|
-
}, [
|
|
480
|
-
let e =
|
|
611
|
+
}, []), I = t((e) => {
|
|
612
|
+
e.preventDefault(), F();
|
|
613
|
+
}, [F]), L = t(() => {
|
|
614
|
+
let e = S.current;
|
|
481
615
|
if (e) {
|
|
482
|
-
if (
|
|
483
|
-
|
|
616
|
+
if (C.current !== e.index) {
|
|
617
|
+
P(), O(!1);
|
|
484
618
|
return;
|
|
485
619
|
}
|
|
486
|
-
|
|
620
|
+
P(), O(!1), b.current(null);
|
|
487
621
|
}
|
|
488
|
-
}, [
|
|
489
|
-
e.target === e.currentTarget && e.animationName === "react-pic-gallery-fade-out" &&
|
|
490
|
-
}, [
|
|
491
|
-
|
|
492
|
-
}, [
|
|
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 =
|
|
495
|
-
e && A !== e.index && (
|
|
496
|
-
}, [A,
|
|
497
|
-
if (!
|
|
498
|
-
let e =
|
|
499
|
-
return
|
|
500
|
-
|
|
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
|
-
}, [
|
|
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
|
|
508
|
-
|
|
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,
|
|
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",
|
|
513
|
-
"aria-labelledby":
|
|
514
|
-
onAnimationEnd:
|
|
515
|
-
onClose:
|
|
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 &&
|
|
651
|
+
e.target === e.currentTarget && F();
|
|
518
652
|
},
|
|
519
|
-
onCancel:
|
|
520
|
-
onKeyDown:
|
|
653
|
+
onCancel: I,
|
|
654
|
+
onKeyDown: M.handleKeyDown,
|
|
521
655
|
children: [/* @__PURE__ */ c("h2", {
|
|
522
656
|
className: "react-pic-gallery__sr-only",
|
|
523
|
-
id:
|
|
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:
|
|
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
|
-
|
|
535
|
-
|
|
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
|
|
542
|
-
let [
|
|
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 ${
|
|
545
|
-
style:
|
|
546
|
-
children: [/* @__PURE__ */ c(
|
|
679
|
+
className: `react-pic-gallery ${a ?? ""}`.trim(),
|
|
680
|
+
style: u,
|
|
681
|
+
children: [/* @__PURE__ */ c(H, {
|
|
547
682
|
images: e,
|
|
548
683
|
layout: t,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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:
|
|
559
|
-
onIndexChange:
|
|
560
|
-
renderActions:
|
|
561
|
-
renderCaption:
|
|
562
|
-
renderControls:
|
|
563
|
-
showCounter:
|
|
564
|
-
showNavigation:
|
|
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 {
|
|
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
|
+
"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",
|