react-pic-gallery 2.0.0 → 2.0.2

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,9 +3,12 @@
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/7.7%20KB/blue)](https://bundlephobia.com/package/react-pic-gallery@2.0.2)
6
7
 
7
8
  [Live playground and docs](https://marcelrsoub.github.io/react-pic-gallery/)
8
9
 
10
+ ![react-pic-gallery displaying a three-column gallery of landscape photos](screenshot.png)
11
+
9
12
  ## Install
10
13
 
11
14
  ```bash
@@ -20,7 +23,17 @@ bun add react-pic-gallery
20
23
 
21
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.
22
25
 
23
- The package is intentionally lightweight: it has no runtime dependencies, React is a peer dependency, and the built JavaScript and CSS together are roughly 6 KB gzipped.
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 **7.7 KB gzipped** (5.55 KB JS + 2.16 KB CSS).
27
+
28
+ ## Why react-pic-gallery
29
+
30
+ - **Tiny by design** — no runtime dependencies, no context providers, no polyfills; ~7.7 KB gzipped total.
31
+ - **Simple by default** — one component, one stylesheet import, sensible accessible defaults.
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
+ - **Accessible** — native modal `<dialog>`, focus containment and restoration, Escape to close, screen-reader announcements, `prefers-reduced-motion` support.
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
+ - **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.
24
37
 
25
38
  ## Quick start
26
39
 
@@ -44,6 +57,21 @@ export function App() {
44
57
 
45
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.
46
59
 
60
+ ## Gallery layouts
61
+
62
+ The default is the existing three-column grid. Choose another layout with `layout`:
63
+
64
+ ```tsx
65
+ <PicGallery images={images} layout='justified' />
66
+ <PicGallery images={images} layout='mosaic' />
67
+ ```
68
+
69
+ - **`grid`** — consistent tiles with a configurable fixed column count.
70
+ - **`justified`** — proportional photos arranged in aligned rows; the final row stays left-aligned.
71
+ - **`mosaic`** — alternating featured photos and smaller supporting tiles.
72
+
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.
74
+
47
75
  ## Custom UI
48
76
 
49
77
  Add actions without rebuilding the lightbox:
@@ -104,9 +132,11 @@ The lightbox uses the native modal `<dialog>` element and includes:
104
132
  - Backdrop closing with stable page width while scrolling is locked.
105
133
  - Native lazy loading for thumbnails and explicit image loading/error states.
106
134
  - Horizontal swipe navigation on touch devices.
107
- - Pinch zoom up to 3x with one-finger panning while zoomed.
135
+ - 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).
108
136
  - Reduced-motion support, safe-area padding, rounded image surfaces, and animated transitions.
109
137
 
138
+ Gallery tiles use roving focus. Left/Right follow image order; in the grid, Up/Down jump a row, and in justified and mosaic layouts they follow the nearest tile position. Home/End move to the first/last image. Enter opens the lightbox, and focus returns to the same tile when it closes.
139
+
110
140
  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.
111
141
 
112
142
  ## Styling
@@ -123,7 +153,7 @@ The stylesheet uses namespaced classes and CSS variables. Import it once, then o
123
153
  }
124
154
  ```
125
155
 
126
- The default gallery uses three columns. Pass `columns` for a different fixed count; `rowHeight` accepts CSS height values or numeric pixels.
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.
127
157
 
128
158
  ## API
129
159
 
@@ -132,15 +162,16 @@ The default gallery uses three columns. Pass `columns` for a different fixed cou
132
162
  | Prop | Type | Description |
133
163
  | --- | --- | --- |
134
164
  | `images` | `readonly GalleryImage[]` | Images to display. |
135
- | `columns` | `number` | Optional fixed column count. The default is three columns. |
136
- | `rowHeight` | `CSSProperties['height']` | Thumbnail height. |
165
+ | `layout` | `'grid' \| 'justified' \| 'mosaic'` | Defaults to `'grid'`. |
166
+ | `columns` | `number` | Optional fixed grid column count. Defaults to three; only applies to `grid`. |
167
+ | `rowHeight` | `CSSProperties['height']` | Grid tile height, mosaic base row height, or justified target row height. |
137
168
  | `renderActions` | `LightboxRenderer` | Adds controls to the default toolbar. |
138
169
  | `renderCaption` | `LightboxRenderer` | Replaces the current caption. |
139
170
  | `renderControls` | `LightboxRenderer` | Replaces the complete default control layer. |
140
171
  | `showCounter` | `boolean` | Shows the current image count. Defaults to `true`. |
141
172
  | `showNavigation` | `boolean` | Shows previous/next controls. Defaults to `true`. |
142
173
 
143
- `Gallery` accepts `images`, `onImageClick`, `columns`, `rowHeight`, `className`, and `style`.
174
+ `Gallery` accepts `images`, `onImageClick`, `layout`, `columns`, `rowHeight`, `className`, and `style`.
144
175
 
145
176
  `Lightbox` accepts `images`, controlled `index`, `onIndexChange`, the renderer props, `showCounter`, `showNavigation`, and `className`.
146
177
 
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, columns, rowHeight, className, style }: GalleryProps<T>): import('react').JSX.Element;
2
+ declare function GalleryComponent<T extends GalleryImage>({ images, onImageClick, layout, columns, rowHeight, className, style }: GalleryProps<T>): import('react').JSX.Element;
3
3
  export declare const Gallery: typeof GalleryComponent;
4
4
  export {};
@@ -1,2 +1,2 @@
1
1
  import { GalleryImage, PicGalleryProps } from './types';
2
- export declare function PicGallery<T extends GalleryImage>({ images, 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, columns, rowHeight, className, galleryClassName, style, renderActions, renderCaption, renderControls, showCounter, showNavigation }: PicGalleryProps<T>): import('react').JSX.Element;
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, GalleryProps, LightboxContext, LightboxProps, LightboxRenderer, PicGalleryProps } from './types';
4
+ export type { GalleryImage, GalleryLayout, GalleryProps, LightboxContext, LightboxProps, LightboxRenderer, PicGalleryProps } from './types';
5
5
  export { PicGallery as default } from './PicGallery';
@@ -0,0 +1,9 @@
1
+ import { GalleryImage } from './types';
2
+ export declare const FALLBACK_ASPECT_RATIO: number;
3
+ export type JustifiedRow = {
4
+ indices: number[];
5
+ widths: number[];
6
+ height: number;
7
+ };
8
+ export declare function getAspectRatio(image: Pick<GalleryImage, 'width' | 'height'>): number;
9
+ export declare function buildJustifiedRows(images: readonly Pick<GalleryImage, 'width' | 'height'>[], containerWidth: number, targetHeight: number, gap: number): JustifiedRow[];
@@ -1,24 +1,24 @@
1
- import { memo as e, useCallback as t, useEffect as n, useId as r, useRef as i, useState as a } from "react";
2
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
3
- import { createPortal as c } from "react-dom";
1
+ import { memo as e, useCallback as t, useEffect as n, useId as r, useLayoutEffect as i, useRef as a, useState as o } from "react";
2
+ import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
3
+ import { createPortal as u } from "react-dom";
4
4
  //#region src/lib/Image.tsx
5
- function l({ src: e, alt: t, className: r = "", imageClassName: c = "", style: l, imageStyle: u, onAnimationEnd: d, eager: f = !1, objectFit: p = "cover", width: m, height: h }) {
6
- let g = i(null), [_, v] = a("loading");
5
+ function d({ src: e, alt: t, className: r = "", imageClassName: i = "", style: s, imageStyle: u, onAnimationEnd: d, eager: f = !1, objectFit: p = "cover", width: m, height: h }) {
6
+ let g = a(null), [_, v] = o("loading");
7
7
  return n(() => {
8
8
  v("loading");
9
9
  let e = g.current;
10
10
  e?.complete && v(e.naturalWidth > 0 ? "loaded" : "error");
11
- }, [e]), /* @__PURE__ */ s("span", {
11
+ }, [e]), /* @__PURE__ */ l("span", {
12
12
  className: `react-pic-gallery__image ${r}`.trim(),
13
- style: l,
13
+ style: s,
14
14
  "aria-busy": _ === "loading",
15
15
  onAnimationEnd: d,
16
16
  children: [
17
- /* @__PURE__ */ o("img", {
17
+ /* @__PURE__ */ c("img", {
18
18
  ref: g,
19
19
  src: e,
20
20
  alt: t,
21
- className: `react-pic-gallery__image-element ${c}`.trim(),
21
+ className: `react-pic-gallery__image-element ${i}`.trim(),
22
22
  style: {
23
23
  objectFit: p,
24
24
  ...u
@@ -30,12 +30,12 @@ function l({ src: e, alt: t, className: r = "", imageClassName: c = "", style: l
30
30
  onLoad: () => v("loaded"),
31
31
  onError: () => v("error")
32
32
  }),
33
- _ === "loading" && /* @__PURE__ */ o("span", {
33
+ _ === "loading" && /* @__PURE__ */ c("span", {
34
34
  className: "react-pic-gallery__image-loader",
35
35
  role: "progressbar",
36
36
  "aria-label": "Loading image"
37
37
  }),
38
- _ === "error" && /* @__PURE__ */ o("span", {
38
+ _ === "error" && /* @__PURE__ */ c("span", {
39
39
  className: "react-pic-gallery__image-error",
40
40
  role: "status",
41
41
  children: "Unable to load image"
@@ -44,332 +44,412 @@ function l({ src: e, alt: t, className: r = "", imageClassName: c = "", style: l
44
44
  });
45
45
  }
46
46
  //#endregion
47
+ //#region src/lib/layout.ts
48
+ var f = 3 / 2;
49
+ function p(e) {
50
+ 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 : f;
51
+ }
52
+ function m(e, t, n, r) {
53
+ if (e.length === 0) return [];
54
+ let i = Math.max(1, t), a = Math.max(1, n), o = Math.max(0, r), s = e.map(p), c = [], l = 0;
55
+ for (; l < e.length;) {
56
+ let t = l + 1, n = i / s[l], r = Math.abs(n - a), u = s[l];
57
+ for (let c = l + 1; c < e.length; c += 1) {
58
+ let e = u + s[c], d = (i - o * (c - l + 1 - 1)) / e, f = Math.abs(d - a);
59
+ if (f < r) t = c + 1, n = d, r = f;
60
+ else if (d < a) break;
61
+ u = e;
62
+ }
63
+ let d = t - l, f = s.slice(l, t).reduce((e, t) => e + t, 0), p = t === e.length ? Math.min(a, (i - o * (d - 1)) / f) : n;
64
+ c.push({
65
+ indices: Array.from({ length: d }, (e, t) => l + t),
66
+ widths: s.slice(l, t).map((e) => e * p),
67
+ height: p
68
+ }), l = t;
69
+ }
70
+ return c;
71
+ }
72
+ //#endregion
47
73
  //#region src/lib/Gallery.tsx
48
- function u({ images: e, onImageClick: t, columns: n, rowHeight: r, className: i = "", style: a }) {
49
- let s = typeof r == "number" ? `${r}px` : r, c = {
50
- ...a,
51
- ...n ? { gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` } : {},
52
- ...s ? { "--gallery-row-height": s } : {}
74
+ var h = 3, g = 192, _ = 12, v = typeof window > "u" ? n : i;
75
+ function y({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeight: u, className: f = "", style: p }) {
76
+ let y = a([]), b = a(null), x = a(null), [S, C] = o(0), [w, T] = o({
77
+ width: 1024,
78
+ rowHeight: g,
79
+ gap: _
80
+ }), E = i !== void 0 && Number.isFinite(i) && i >= 1 ? Math.floor(i) : h, D = e.length - 1, O = Math.min(S, Math.max(D, 0)), k = typeof u == "number" ? `${u}px` : u, A = {
81
+ ...p,
82
+ ...r === "grid" && i !== void 0 && Number.isFinite(i) && i >= 1 ? { gridTemplateColumns: `repeat(${E}, minmax(0, 1fr))` } : {},
83
+ ...k ? { "--gallery-row-height": k } : {}
53
84
  };
54
- return /* @__PURE__ */ o("div", {
55
- className: `react-pic-gallery__grid ${i}`.trim(),
56
- style: c,
57
- "data-testid": "react-pic-gallery-grid",
58
- children: e.map((e, n) => /* @__PURE__ */ o("button", {
59
- className: "react-pic-gallery__tile",
60
- type: "button",
61
- disabled: !t,
62
- onClick: () => t?.(n),
63
- "aria-label": `Open ${e.alt}`,
64
- children: /* @__PURE__ */ o(l, {
65
- src: e.thumbnailSrc ?? e.src,
66
- alt: e.alt,
67
- objectFit: "cover",
68
- width: e.width,
69
- height: e.height
85
+ v(() => {
86
+ if (r !== "justified") return;
87
+ let e = b.current, t = x.current;
88
+ if (!e || !t) return;
89
+ let n = () => {
90
+ 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) : g;
91
+ T((e) => {
92
+ let t = {
93
+ width: n,
94
+ rowHeight: r.height || a,
95
+ gap: Number.isFinite(i) ? i : r.width || _
96
+ };
97
+ return e.width === t.width && e.rowHeight === t.rowHeight && e.gap === t.gap ? e : t;
98
+ });
99
+ };
100
+ n();
101
+ let i = typeof ResizeObserver > "u" ? null : new ResizeObserver(n);
102
+ return i?.observe(e), i?.observe(t), window.addEventListener("resize", n), () => {
103
+ i?.disconnect(), window.removeEventListener("resize", n);
104
+ };
105
+ }, [
106
+ r,
107
+ u,
108
+ k
109
+ ]);
110
+ let j = t((e) => {
111
+ C(e), y.current[e]?.focus();
112
+ }, []), M = t((t, n) => {
113
+ let i = y.current[t];
114
+ if (!i) return null;
115
+ let a = i.getBoundingClientRect();
116
+ if (r === "mosaic" && i.classList.contains("react-pic-gallery__tile--mosaic-featured")) {
117
+ 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;
118
+ if (y.current.slice(0, e.length).forEach((e, i) => {
119
+ if (!e || i === t || !r?.contains(e)) return;
120
+ let a = e.getBoundingClientRect(), u = a.left + a.width / 2, d = a.top + a.height / 2, f = n === "up" ? s - d : d - s;
121
+ if (f <= 0) return;
122
+ let p = f + Math.abs(u - o);
123
+ p < l && (l = p, c = i);
124
+ }), c !== null) return c;
125
+ }
126
+ let o = null, s = Infinity;
127
+ return y.current.slice(0, e.length).forEach((e, r) => {
128
+ if (!e || r === t) return;
129
+ let i = e.getBoundingClientRect(), c = n === "up" ? a.top - i.bottom : i.top - a.bottom;
130
+ if (c < 0) return;
131
+ let l = c + (i.right < a.left ? a.left - i.right : a.right < i.left ? i.left - a.right : 0);
132
+ l < s && (s = l, o = r);
133
+ }), o;
134
+ }, [e.length, r]), N = t((e, t) => {
135
+ let n = null;
136
+ if (e.key === "Home") n = 0;
137
+ else if (e.key === "End") n = D;
138
+ else if (r === "grid") {
139
+ let r = {
140
+ ArrowRight: 1,
141
+ ArrowLeft: -1,
142
+ ArrowDown: E,
143
+ ArrowUp: -E
144
+ }[e.key];
145
+ if (r !== void 0) {
146
+ let e = t + r;
147
+ e >= 0 && e <= D && (n = e);
148
+ }
149
+ } else if (e.key === "ArrowRight" && t < D) n = t + 1;
150
+ else if (e.key === "ArrowLeft" && t > 0) n = t - 1;
151
+ else if (e.key === "ArrowDown") n = M(t, "down");
152
+ else if (e.key === "ArrowUp") n = M(t, "up");
153
+ else return;
154
+ n !== null && n !== t && (e.preventDefault(), j(n));
155
+ }, [
156
+ E,
157
+ M,
158
+ D,
159
+ r,
160
+ j
161
+ ]), P = (e, t, r = {}) => /* @__PURE__ */ c("button", {
162
+ className: `react-pic-gallery__tile ${r.className ?? ""}`.trim(),
163
+ type: "button",
164
+ disabled: !n,
165
+ tabIndex: t === O ? 0 : -1,
166
+ ref: (e) => {
167
+ y.current[t] = e;
168
+ },
169
+ style: r.style,
170
+ onKeyDown: (e) => N(e, t),
171
+ onClick: () => {
172
+ C(t), n?.(t);
173
+ },
174
+ "aria-label": `Open ${e.alt}`,
175
+ children: /* @__PURE__ */ c(d, {
176
+ src: e.thumbnailSrc ?? e.src,
177
+ alt: e.alt,
178
+ objectFit: "cover",
179
+ width: e.width,
180
+ height: e.height
181
+ })
182
+ }, e.id ?? `${e.src}-${t}`), F = r === "justified" ? m(e, w.width, w.rowHeight, w.gap) : [], I = Array(e.length);
183
+ return F.forEach((e) => {
184
+ e.indices.forEach((t, n) => {
185
+ I[t] = {
186
+ width: `${e.widths[n]}px`,
187
+ height: `${e.height}px`
188
+ };
189
+ });
190
+ }), /* @__PURE__ */ l("div", {
191
+ ref: b,
192
+ className: `react-pic-gallery__gallery react-pic-gallery__gallery--${r} ${r === "grid" ? "react-pic-gallery__grid" : ""} ${f}`.trim(),
193
+ style: A,
194
+ "data-testid": r === "grid" ? "react-pic-gallery-grid" : void 0,
195
+ "data-layout": r,
196
+ children: [
197
+ r === "grid" && e.map((e, t) => P(e, t)),
198
+ r === "justified" && /* @__PURE__ */ l(s, { children: [e.map((e, t) => P(e, t, {
199
+ className: "react-pic-gallery__tile--justified",
200
+ style: I[t]
201
+ })), /* @__PURE__ */ c("span", {
202
+ ref: x,
203
+ className: "react-pic-gallery__row-height-probe",
204
+ "aria-hidden": "true"
205
+ })] }),
206
+ r === "mosaic" && Array.from({ length: Math.ceil(e.length / 3) }, (t, n) => {
207
+ let r = n * 3, i = e.slice(r, r + 3), a = n % 2 == 0 ? "left" : "top";
208
+ return /* @__PURE__ */ c("div", {
209
+ className: `react-pic-gallery__mosaic-group react-pic-gallery__mosaic-group--${a} react-pic-gallery__mosaic-group--count-${i.length}`,
210
+ children: i.map((e, t) => {
211
+ let n = r + t, a = i.length === 3 ? t === 0 ? "featured" : "supporting" : "simple";
212
+ return P(e, n, { className: `react-pic-gallery__tile--mosaic react-pic-gallery__tile--mosaic-${a}` });
213
+ })
214
+ }, `mosaic-${r}`);
70
215
  })
71
- }, e.id ?? `${e.src}-${n}`))
216
+ ]
72
217
  });
73
218
  }
74
- var d = e(u), f = /* @__PURE__ */ new Set(), p = null, m = "a[href], button, input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]", h = ".react-pic-gallery__image-element", g = 3;
75
- function _(e) {
76
- return e instanceof Element && e.closest(m) !== null;
77
- }
78
- function v(e, t, n) {
79
- return Math.min(Math.max(e, t), n);
219
+ var b = e(y), x = /* @__PURE__ */ new Set(), S = null, C = "a[href], button, input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]", w = "input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]", T = "(max-width: 600px), (pointer: coarse)", E = .3;
220
+ function D(e) {
221
+ return e instanceof Element && e.closest(C) !== null;
80
222
  }
81
- function ee(e) {
82
- if (!f.has(e)) {
83
- if (f.size === 0) {
223
+ function O(e) {
224
+ if (!x.has(e)) {
225
+ if (x.size === 0) {
84
226
  let e = document.body, t = window.innerWidth - document.documentElement.clientWidth, n = Number.parseFloat(window.getComputedStyle(e).paddingRight) || 0;
85
- p = {
227
+ S = {
86
228
  overflow: e.style.overflow,
87
229
  paddingRight: e.style.paddingRight
88
230
  }, e.style.overflow = "hidden", t > 0 && (e.style.paddingRight = `${n + t}px`);
89
231
  }
90
- f.add(e);
232
+ x.add(e);
91
233
  }
92
234
  }
93
- function te(e) {
94
- return f.delete(e), f.size > 0 || !p ? !1 : (document.body.style.overflow = p.overflow, document.body.style.paddingRight = p.paddingRight, p = null, !0);
235
+ function k(e) {
236
+ return x.delete(e), x.size > 0 || !S ? !1 : (document.body.style.overflow = S.overflow, document.body.style.paddingRight = S.paddingRight, S = null, !0);
95
237
  }
96
- function y(e) {
238
+ function A(e) {
97
239
  let t = e.trim();
98
240
  return t.endsWith("ms") ? Number.parseFloat(t) : t.endsWith("s") ? Number.parseFloat(t) * 1e3 : 0;
99
241
  }
100
- function ne(e) {
242
+ function j(e) {
101
243
  let t = window.getComputedStyle(e), n = t.animationDuration.split(","), r = t.animationDelay.split(",");
102
- return Math.max(...n.map((e, t) => y(e) + y(r[t % r.length] ?? "0s")), 0);
244
+ return Math.max(...n.map((e, t) => A(e) + A(r[t % r.length] ?? "0s")), 0);
103
245
  }
104
- function re() {
105
- return /* @__PURE__ */ o("svg", {
246
+ function M() {
247
+ return /* @__PURE__ */ c("svg", {
106
248
  viewBox: "0 0 24 24",
107
249
  "aria-hidden": "true",
108
250
  focusable: "false",
109
- children: /* @__PURE__ */ o("path", { d: "M6 6l12 12M18 6L6 18" })
251
+ children: /* @__PURE__ */ c("path", { d: "M6 6l12 12M18 6L6 18" })
110
252
  });
111
253
  }
112
- function b({ direction: e }) {
113
- return /* @__PURE__ */ o("svg", {
254
+ function N({ direction: e }) {
255
+ return /* @__PURE__ */ c("svg", {
114
256
  viewBox: "0 0 24 24",
115
257
  "aria-hidden": "true",
116
258
  focusable: "false",
117
- children: /* @__PURE__ */ o("path", { d: e === "left" ? "M14.5 5l-7 7 7 7" : "M9.5 5l7 7-7 7" })
259
+ children: /* @__PURE__ */ c("path", { d: e === "left" ? "M14.5 5l-7 7 7 7" : "M9.5 5l7 7-7 7" })
118
260
  });
119
261
  }
120
- function x(...e) {
262
+ function P(...e) {
121
263
  return e.filter(Boolean).join(" ");
122
264
  }
123
- function S({ images: e, index: u, onIndexChange: d, renderActions: f, renderCaption: p, renderControls: y, showCounter: S = !0, showNavigation: C = !0, className: ie }) {
124
- let w = i(null), T = i(d), E = i(null), D = i(null), O = i(null), k = i(!1), A = i(!1), j = i({}), M = i(e), N = i(null), P = i(/* @__PURE__ */ new Map()), F = i(null), I = i(null), L = r(), [R, z] = a(!1), [B, V] = a(null), [H, U] = a(1), [W, G] = a({
125
- x: 0,
126
- y: 0
127
- }), [ae, oe] = a("50% 50%"), K = u !== null && u >= 0 && u < e.length ? u : null, q = K === null ? void 0 : e[K];
128
- T.current = d, O.current = K, k.current = R, M.current = e;
129
- let J = t(() => {
130
- E.current !== null && (window.clearTimeout(E.current), E.current = null), D.current = null;
131
- }, []), Y = t(() => {
132
- let e = O.current;
133
- if (e !== null && D.current === null) {
265
+ function F({ images: e, index: i, onIndexChange: s, renderActions: f, renderCaption: p, renderControls: m, showCounter: h = !0, showNavigation: g = !0, className: _ }) {
266
+ let v = a(null), y = a(s), b = a(null), x = a(null), S = a(null), C = a(!1), A = a(!1), F = a({}), I = a(e), L = a(null), R = r(), [z, B] = o(!1), [V, H] = o(null), U = i !== null && i >= 0 && i < e.length ? i : null, W = U === null ? void 0 : e[U];
267
+ y.current = s, S.current = U, C.current = z, I.current = e;
268
+ let G = t(() => {
269
+ b.current !== null && (window.clearTimeout(b.current), b.current = null), x.current = null;
270
+ }, []), K = t(() => {
271
+ let e = S.current;
272
+ if (e !== null && x.current === null) {
134
273
  if (typeof window < "u" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
135
- T.current(null);
274
+ y.current(null);
136
275
  return;
137
276
  }
138
- D.current = { index: e }, k.current = !0, z(!0);
277
+ x.current = { index: e }, C.current = !0, B(!0);
139
278
  }
279
+ }, []), q = t(() => {
280
+ let e = S.current;
281
+ !C.current && e !== null && e > 0 && y.current(e - 1);
282
+ }, []), J = t(() => {
283
+ let e = S.current;
284
+ !C.current && e !== null && e < I.current.length - 1 && y.current(e + 1);
285
+ }, []), Y = t(() => {
286
+ let e = S.current;
287
+ C.current || e === null || e === 0 || (H("previous"), y.current(e - 1));
140
288
  }, []), X = t(() => {
141
- let e = O.current;
142
- !k.current && e !== null && e > 0 && T.current(e - 1);
143
- }, []), Z = t(() => {
144
- let e = O.current;
145
- !k.current && e !== null && e < M.current.length - 1 && T.current(e + 1);
146
- }, []), se = t(() => {
147
- let e = O.current;
148
- k.current || e === null || e === 0 || (V("previous"), T.current(e - 1));
149
- }, []), ce = t(() => {
150
- let e = O.current;
151
- k.current || e === null || e >= M.current.length - 1 || (V("next"), T.current(e + 1));
152
- }, []), le = t((e) => {
153
- e.preventDefault(), Y();
154
- }, [Y]), Q = t(() => {
155
- let e = D.current;
289
+ let e = S.current;
290
+ C.current || e === null || e >= I.current.length - 1 || (H("next"), y.current(e + 1));
291
+ }, []), ee = t((e) => {
292
+ e.preventDefault(), K();
293
+ }, [K]), Z = t(() => {
294
+ let e = x.current;
156
295
  if (e) {
157
- if (O.current !== e.index) {
158
- J(), k.current = !1, z(!1);
296
+ if (S.current !== e.index) {
297
+ G(), C.current = !1, B(!1);
159
298
  return;
160
299
  }
161
- J(), k.current = !1, z(!1), T.current(null);
300
+ G(), C.current = !1, B(!1), y.current(null);
162
301
  }
163
- }, [J]), ue = t((e) => {
302
+ }, [G]), te = t((e) => {
164
303
  if (e.defaultPrevented) return;
165
304
  let t = e.target;
166
- t instanceof HTMLElement && (t.isContentEditable || t.closest(m)) || (e.key === "ArrowLeft" ? (e.preventDefault(), X()) : e.key === "ArrowRight" && (e.preventDefault(), Z()));
167
- }, [Z, X]), de = t((e) => {
168
- if (H > 1 || e.touches.length !== 1 || _(e.target)) {
169
- N.current = null;
305
+ t instanceof HTMLElement && (t.isContentEditable || t.closest(w)) || (e.key === "ArrowLeft" ? (e.preventDefault(), q()) : e.key === "ArrowRight" && (e.preventDefault(), J()));
306
+ }, [J, q]), ne = t((e) => {
307
+ if (e.touches.length !== 1 || D(e.target)) {
308
+ L.current = null;
170
309
  return;
171
310
  }
172
311
  let t = e.touches[0];
173
- N.current = {
312
+ L.current = {
174
313
  x: t.clientX,
175
314
  y: t.clientY
176
315
  };
177
- }, [H]), fe = t((e) => {
178
- let t = N.current;
179
- if (N.current = null, !t || e.changedTouches.length === 0) return;
316
+ }, []), re = t((e) => {
317
+ let t = L.current;
318
+ if (L.current = null, !t || e.changedTouches.length === 0) return;
180
319
  let n = e.changedTouches[0], r = n.clientX - t.x, i = n.clientY - t.y, a = Math.abs(r);
181
- a < 48 || a <= Math.abs(i) || (r < 0 ? ce() : se());
182
- }, [
183
- ce,
184
- se,
185
- H
186
- ]), pe = t(() => {
187
- N.current = null;
188
- }, []), me = t((e) => {
189
- if (!(e.target instanceof Element) || e.target.closest(h) === null || e.pointerType === "mouse" && e.button !== 0) return;
190
- let t = P.current;
191
- if (t.set(e.pointerId, {
192
- x: e.clientX,
193
- y: e.clientY
194
- }), e.currentTarget.setPointerCapture?.(e.pointerId), t.size === 2) {
195
- let [n, r] = [...t.values()], i = Math.hypot(r.x - n.x, r.y - n.y), a = e.currentTarget.querySelector(h)?.getBoundingClientRect(), o = (n.x + r.x) / 2, s = (n.y + r.y) / 2;
196
- a && a.width > 0 && a.height > 0 && oe(`${v((o - a.left) / a.width * 100, 0, 100)}% ${v((s - a.top) / a.height * 100, 0, 100)}%`), F.current = {
197
- distance: Math.max(i, 1),
198
- scale: H,
199
- offset: W
200
- }, I.current = null;
201
- } else H > 1 && (I.current = {
202
- x: e.clientX,
203
- y: e.clientY,
204
- offset: W
205
- });
206
- }, [W, H]), he = t((e) => {
207
- let t = P.current;
208
- if (t.has(e.pointerId)) {
209
- if (t.set(e.pointerId, {
210
- x: e.clientX,
211
- y: e.clientY
212
- }), t.size >= 2 && F.current) {
213
- let [n, r] = [...t.values()], i = Math.max(Math.hypot(r.x - n.x, r.y - n.y), 1), a = v(F.current.scale * (i / F.current.distance), 1, g);
214
- U(a), G(F.current.offset), e.preventDefault();
215
- return;
216
- }
217
- if (t.size === 1 && I.current && H > 1) {
218
- let t = e.currentTarget.querySelector(h), n = t?.clientWidth ?? 0, r = t?.clientHeight ?? 0, i = Math.max(0, n * (H - 1) / 2), a = Math.max(0, r * (H - 1) / 2), o = I.current.offset.x + e.clientX - I.current.x, s = I.current.offset.y + e.clientY - I.current.y;
219
- G({
220
- x: v(o, -i, i),
221
- y: v(s, -a, a)
222
- }), e.preventDefault();
223
- }
224
- }
225
- }, [H]), ge = t((e) => {
226
- if (P.current.delete(e.pointerId), e.currentTarget.hasPointerCapture?.(e.pointerId) && e.currentTarget.releasePointerCapture(e.pointerId), P.current.size < 2 && (F.current = null), P.current.size === 1 && H > 1) {
227
- let [e] = [...P.current.values()];
228
- I.current = {
229
- x: e.x,
230
- y: e.y,
231
- offset: W
232
- };
233
- } else I.current = null;
234
- }, [W, H]), _e = t((e) => {
235
- e.target === e.currentTarget && V(null);
236
- }, []), ve = t((e) => {
237
- e.target === e.currentTarget && e.animationName === "react-pic-gallery-fade-out" && Q();
238
- }, [Q]), ye = t(() => {
239
- A.current || (J(), k.current = !1, z(!1), O.current !== null && T.current(null));
240
- }, [J]);
320
+ a < 48 || a <= Math.abs(i) || (r < 0 ? X() : Y());
321
+ }, [X, Y]), ie = t(() => {
322
+ L.current = null;
323
+ }, []), ae = t((e) => {
324
+ e.target === e.currentTarget && H(null);
325
+ }, []), oe = t((e) => {
326
+ e.target === e.currentTarget && e.animationName === "react-pic-gallery-fade-out" && Z();
327
+ }, [Z]), se = t(() => {
328
+ A.current || (G(), C.current = !1, B(!1), S.current !== null && y.current(null));
329
+ }, [G]);
241
330
  if (n(() => {
242
- U(1), G({
243
- x: 0,
244
- y: 0
245
- }), oe("50% 50%"), P.current.clear(), F.current = null, I.current = null;
246
- }, [K]), n(() => {
247
- let e = D.current;
248
- e && K !== e.index && (J(), k.current = !1, z(!1));
249
- }, [K, J]), n(() => {
250
- if (!R || !D.current || !w.current) return;
251
- let e = ne(w.current);
252
- return E.current = window.setTimeout(Q, e), () => {
253
- E.current !== null && (window.clearTimeout(E.current), E.current = null);
331
+ let e = x.current;
332
+ e && U !== e.index && (G(), C.current = !1, B(!1));
333
+ }, [U, G]), n(() => {
334
+ if (!z || !x.current || !v.current) return;
335
+ let e = j(v.current);
336
+ return b.current = window.setTimeout(Z, e), () => {
337
+ b.current !== null && (window.clearTimeout(b.current), b.current = null);
254
338
  };
255
- }, [Q, R]), n(() => {
256
- if (K === null) return;
257
- let e = w.current;
339
+ }, [Z, z]), n(() => {
340
+ if (U === null) return;
341
+ let e = v.current;
258
342
  if (!e) return;
259
343
  let t = document.activeElement;
260
- return ee(j.current), e.open || e.showModal(), () => {
261
- J(), A.current = !0, e.open && e.close(), A.current = !1, te(j.current) && t?.isConnected && t.focus();
344
+ return O(F.current), e.open || e.showModal(), () => {
345
+ G(), A.current = !0, e.open && e.close(), A.current = !1, k(F.current) && t?.isConnected && t.focus();
262
346
  };
263
- }, [K !== null, J]), K === null || !q || typeof document > "u") return null;
264
- let $ = {
265
- image: q,
266
- index: K,
347
+ }, [U !== null, G]), U === null || !W || typeof document > "u") return null;
348
+ let Q = {
349
+ image: W,
350
+ index: U,
267
351
  count: e.length,
268
- close: Y,
269
- next: Z,
270
- previous: X,
271
- canGoNext: K < e.length - 1,
272
- canGoPrevious: K > 0
273
- }, be = C && !y && $.canGoPrevious, xe = C && !y && $.canGoNext, Se = C && !y && e.length > 1, Ce = (e) => {
274
- e.target === e.currentTarget && Y();
352
+ close: K,
353
+ next: J,
354
+ previous: q,
355
+ canGoNext: U < e.length - 1,
356
+ canGoPrevious: U > 0
357
+ }, ce = g && !m && Q.canGoPrevious, le = g && !m && Q.canGoNext, $ = g && !m && e.length > 1, ue = (e) => {
358
+ e.target === e.currentTarget && K();
275
359
  };
276
- return c(/* @__PURE__ */ s("dialog", {
277
- ref: w,
278
- className: x("react-pic-gallery__lightbox", R ? "react-pic-gallery__lightbox--closing" : void 0, ie),
279
- "aria-labelledby": L,
280
- onAnimationEnd: ve,
281
- onClose: ye,
282
- onMouseDown: Ce,
283
- onCancel: le,
284
- onKeyDown: ue,
360
+ return u(/* @__PURE__ */ l("dialog", {
361
+ ref: v,
362
+ className: P("react-pic-gallery__lightbox", z ? "react-pic-gallery__lightbox--closing" : void 0, _),
363
+ "aria-labelledby": R,
364
+ onAnimationEnd: oe,
365
+ onClose: se,
366
+ onMouseDown: ue,
367
+ onCancel: ee,
368
+ onKeyDown: te,
285
369
  children: [
286
- /* @__PURE__ */ o("h2", {
370
+ /* @__PURE__ */ c("h2", {
287
371
  className: "react-pic-gallery__sr-only",
288
- id: L,
372
+ id: R,
289
373
  children: "Image viewer"
290
374
  }),
291
- /* @__PURE__ */ s("p", {
375
+ /* @__PURE__ */ l("p", {
292
376
  className: "react-pic-gallery__sr-only",
293
377
  "aria-live": "polite",
294
378
  children: [
295
- q.alt,
379
+ W.alt,
296
380
  ", image ",
297
- K + 1,
381
+ U + 1,
298
382
  " of ",
299
383
  e.length
300
384
  ]
301
385
  }),
302
- /* @__PURE__ */ s("div", {
386
+ /* @__PURE__ */ l("div", {
303
387
  className: "react-pic-gallery__lightbox-content",
304
388
  children: [
305
- y ? /* @__PURE__ */ o("div", {
389
+ m ? /* @__PURE__ */ c("div", {
306
390
  className: "react-pic-gallery__custom-controls",
307
- children: y($)
308
- }) : /* @__PURE__ */ s("div", {
391
+ children: m(Q)
392
+ }) : /* @__PURE__ */ l("div", {
309
393
  className: "react-pic-gallery__toolbar",
310
- children: [/* @__PURE__ */ s("div", {
394
+ children: [/* @__PURE__ */ l("div", {
311
395
  className: "react-pic-gallery__toolbar-actions",
312
- children: [S && /* @__PURE__ */ s("span", {
396
+ children: [h && /* @__PURE__ */ l("span", {
313
397
  "aria-live": "polite",
314
398
  children: [
315
- K + 1,
399
+ U + 1,
316
400
  " / ",
317
401
  e.length
318
402
  ]
319
- }), f?.($)]
320
- }), /* @__PURE__ */ o("button", {
403
+ }), f?.(Q)]
404
+ }), /* @__PURE__ */ c("button", {
321
405
  className: "react-pic-gallery__control",
322
406
  type: "button",
323
- onClick: Y,
407
+ onClick: K,
324
408
  "aria-label": "Close image viewer",
325
- children: /* @__PURE__ */ o(re, {})
409
+ children: /* @__PURE__ */ c(M, {})
326
410
  })]
327
411
  }),
328
- /* @__PURE__ */ s("div", {
329
- className: x("react-pic-gallery__viewport", Se ? "react-pic-gallery__viewport--has-navigation" : void 0),
330
- onMouseDown: Ce,
331
- onPointerCancel: ge,
332
- onPointerDown: me,
333
- onPointerMove: he,
334
- onPointerUp: ge,
335
- onTouchCancel: pe,
336
- onTouchEnd: fe,
337
- onTouchStart: de,
412
+ /* @__PURE__ */ l("div", {
413
+ className: P("react-pic-gallery__viewport", $ ? "react-pic-gallery__viewport--has-navigation" : void 0),
414
+ onMouseDown: ue,
415
+ onClick: (e) => {
416
+ if (!$ || C.current || !(e.target instanceof Element) || e.target.closest("button") || window.matchMedia?.(T)?.matches !== !0) return;
417
+ let t = e.currentTarget.getBoundingClientRect();
418
+ if (t.width <= 0) return;
419
+ let n = (e.clientX - t.left) / t.width;
420
+ n < E ? q() : n > .7 && J();
421
+ },
422
+ onTouchCancel: ie,
423
+ onTouchEnd: re,
424
+ onTouchStart: ne,
338
425
  children: [
339
- be && /* @__PURE__ */ o("button", {
426
+ ce && /* @__PURE__ */ c("button", {
340
427
  className: "react-pic-gallery__control react-pic-gallery__control--previous",
341
428
  type: "button",
342
- onClick: X,
429
+ onClick: q,
343
430
  "aria-label": "Previous image",
344
- children: /* @__PURE__ */ o(b, { direction: "left" })
431
+ children: /* @__PURE__ */ c(N, { direction: "left" })
345
432
  }),
346
- /* @__PURE__ */ o(l, {
347
- src: q.src,
348
- alt: q.alt,
433
+ /* @__PURE__ */ c(d, {
434
+ src: W.src,
435
+ alt: W.alt,
349
436
  eager: !0,
350
437
  objectFit: "contain",
351
- width: q.width,
352
- height: q.height,
353
- className: x("react-pic-gallery__lightbox-image", B ? `react-pic-gallery__lightbox-image--swipe-${B}` : void 0),
354
- imageStyle: {
355
- transform: `translate3d(${W.x}px, ${W.y}px, 0) scale(${H})`,
356
- transformOrigin: ae,
357
- transition: P.current.size > 0 ? "none" : void 0
358
- },
359
- onAnimationEnd: _e
360
- }, q.src),
361
- xe && /* @__PURE__ */ o("button", {
438
+ className: P("react-pic-gallery__lightbox-image", V ? `react-pic-gallery__lightbox-image--swipe-${V}` : void 0),
439
+ onAnimationEnd: ae
440
+ }, W.src),
441
+ le && /* @__PURE__ */ c("button", {
362
442
  className: "react-pic-gallery__control react-pic-gallery__control--next",
363
443
  type: "button",
364
- onClick: Z,
444
+ onClick: J,
365
445
  "aria-label": "Next image",
366
- children: /* @__PURE__ */ o(b, { direction: "right" })
446
+ children: /* @__PURE__ */ c(N, { direction: "right" })
367
447
  })
368
448
  ]
369
449
  }),
370
- (p || q.caption) && /* @__PURE__ */ o("div", {
450
+ (p || W.caption) && /* @__PURE__ */ c("div", {
371
451
  className: "react-pic-gallery__caption",
372
- children: p ? p($) : q.caption
452
+ children: p ? p(Q) : W.caption
373
453
  })
374
454
  ]
375
455
  })
@@ -378,23 +458,24 @@ function S({ images: e, index: u, onIndexChange: d, renderActions: f, renderCapt
378
458
  }
379
459
  //#endregion
380
460
  //#region src/lib/PicGallery.tsx
381
- function C({ images: e, columns: t, rowHeight: n, className: r, galleryClassName: i, style: c, renderActions: l, renderCaption: u, renderControls: f, showCounter: p, showNavigation: m }) {
382
- let [h, g] = a(null);
383
- return /* @__PURE__ */ s("div", {
384
- className: `react-pic-gallery ${r ?? ""}`.trim(),
385
- style: c,
386
- children: [/* @__PURE__ */ o(d, {
461
+ 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 }) {
462
+ let [h, g] = o(null);
463
+ return /* @__PURE__ */ l("div", {
464
+ className: `react-pic-gallery ${i ?? ""}`.trim(),
465
+ style: s,
466
+ children: [/* @__PURE__ */ c(b, {
387
467
  images: e,
388
- columns: t,
389
- rowHeight: n,
390
- className: i,
468
+ layout: t,
469
+ columns: n,
470
+ rowHeight: r,
471
+ className: a,
391
472
  onImageClick: g
392
- }), /* @__PURE__ */ o(S, {
473
+ }), /* @__PURE__ */ c(F, {
393
474
  images: e,
394
475
  index: h,
395
476
  onIndexChange: g,
396
- renderActions: l,
397
- renderCaption: u,
477
+ renderActions: u,
478
+ renderCaption: d,
398
479
  renderControls: f,
399
480
  showCounter: p,
400
481
  showNavigation: m
@@ -402,4 +483,4 @@ function C({ images: e, columns: t, rowHeight: n, className: r, galleryClassName
402
483
  });
403
484
  }
404
485
  //#endregion
405
- export { d as Gallery, S as Lightbox, C as PicGallery, C as default };
486
+ export { b as Gallery, F as Lightbox, I as PicGallery, I as default };
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
- :where(.react-pic-gallery,.react-pic-gallery__lightbox){--gallery-gap:.75rem;--gallery-radius:1rem;--gallery-row-height:12rem;--gallery-accent:#9ef01a;--gallery-overlay:#070b10f0;--gallery-control-size:2.75rem;--gallery-motion:.18s;color:#f8fafc}.react-pic-gallery{width:100%;min-width:0}.react-pic-gallery__grid{gap:var(--gallery-gap);grid-template-columns:repeat(3,minmax(0,1fr));width:100%;display:grid}.react-pic-gallery__tile{min-width:0;height:var(--gallery-row-height);cursor:pointer;border-radius:var(--gallery-radius);isolation:isolate;background:#e2e8f0;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) 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);outline-offset:3px}.react-pic-gallery__tile:focus-visible:after{border-color:var(--gallery-accent)}.react-pic-gallery__tile:disabled{cursor:default}.react-pic-gallery__image{background:linear-gradient(135deg,#e2e8f0,#cbd5e1);width:100%;height:100%;display:block;position:relative;overflow:hidden}.react-pic-gallery__image-element{width:100%;height:100%;transition:transform var(--gallery-motion) ease, opacity var(--gallery-motion) ease;display:block}.react-pic-gallery__image-loader,.react-pic-gallery__image-error{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);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:100%;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{width:min(100%,90rem);height:min(100%,54rem);min-height:0;animation:react-pic-gallery-rise-in var(--gallery-motion) ease both;grid-template-rows:auto minmax(0,1fr) auto;display:grid}.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;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;transform:translateY(-1px)}.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;place-items:center;min-height:0;padding:.75rem;display:grid;position:relative}.react-pic-gallery__viewport--has-navigation{padding-inline:3.5rem}.react-pic-gallery__lightbox-image{pointer-events:none;border-radius:var(--gallery-radius);width:min(100%,82rem);min-width:0;height:100%;min-height:0;animation:react-pic-gallery-image-in var(--gallery-motion) ease both;background:0 0;place-items:center;display:grid;overflow:visible}.react-pic-gallery__lightbox-image .react-pic-gallery__image-element{pointer-events:auto;touch-action:none;border-radius:inherit;object-fit:contain;background:0 0;width:auto;max-width:100%;height:auto;max-height:100%}.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:hover,.react-pic-gallery__control--next:hover{transform:translateY(calc(-50% - 1px))}.react-pic-gallery__control--previous{left:0}.react-pic-gallery__control--next{right:0}.react-pic-gallery__caption{color:#cbd5e1;text-align:center;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}.react-pic-gallery__lightbox-content{height:100%}.react-pic-gallery__viewport{padding-inline:.25rem}.react-pic-gallery__viewport--has-navigation{padding-inline:2.75rem}.react-pic-gallery__control--previous{left:-.25rem}.react-pic-gallery__control--next{right:-.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){--gallery-gap:.75rem;--gallery-radius:1rem;--gallery-row-height:12rem;--gallery-accent:#9ef01a;--gallery-overlay:#070b10f0;--gallery-control-size:2.75rem;--gallery-motion:.18s;color:#f8fafc}.react-pic-gallery{width:100%;min-width:0}.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:#e2e8f0;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{background:linear-gradient(135deg,#e2e8f0,#cbd5e1);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{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}: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}}
2
2
  /*$vite$:1*/
package/dist/types.d.ts CHANGED
@@ -8,6 +8,7 @@ export type GalleryImage = {
8
8
  width?: number;
9
9
  height?: number;
10
10
  };
11
+ export type GalleryLayout = 'grid' | 'justified' | 'mosaic';
11
12
  export type LightboxContext<T extends GalleryImage = GalleryImage> = {
12
13
  image: T;
13
14
  index: number;
@@ -22,6 +23,7 @@ export type LightboxRenderer<T extends GalleryImage = GalleryImage> = (context:
22
23
  export type GalleryProps<T extends GalleryImage = GalleryImage> = {
23
24
  images: readonly T[];
24
25
  onImageClick?: (index: number) => void;
26
+ layout?: GalleryLayout;
25
27
  columns?: number;
26
28
  rowHeight?: CSSProperties['height'];
27
29
  className?: string;
@@ -40,6 +42,7 @@ export type LightboxProps<T extends GalleryImage = GalleryImage> = {
40
42
  };
41
43
  export type PicGalleryProps<T extends GalleryImage = GalleryImage> = {
42
44
  images: readonly T[];
45
+ layout?: GalleryLayout;
43
46
  columns?: number;
44
47
  rowHeight?: CSSProperties['height'];
45
48
  className?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-pic-gallery",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Small, accessible React image gallery and lightbox.",
5
5
  "author": "marcelrsoub",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "dev:docs": "astro dev --config docs-site/astro.config.mjs",
37
37
  "build:docs": "astro build --config docs-site/astro.config.mjs",
38
38
  "preview:docs": "astro preview --config docs-site/astro.config.mjs",
39
- "github-pages": "npm run build:docs && gh-pages -d build",
39
+ "github-pages": "npm run build:docs && gh-pages -d build --dotfiles",
40
40
  "prepack": "npm run build"
41
41
  },
42
42
  "devDependencies": {