react-pic-gallery 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Small, accessible React image gallery and lightbox with a polished default UI and typed escape hatches for custom controls.
4
4
 
5
5
  [![NPM](https://img.shields.io/npm/v/react-pic-gallery.svg)](https://www.npmjs.com/package/react-pic-gallery)
6
- [![Minified + gzip size](https://badgen.net/static/minified%20%2B%20gzip/7.7%20KB/blue)](https://bundlephobia.com/package/react-pic-gallery@2.0.2)
6
+ [![Minified + gzip size](https://badgen.net/static/minified%20%2B%20gzip/8.5%20KB/blue)](https://bundlephobia.com/package/react-pic-gallery)
7
7
 
8
8
  [Live playground and docs](https://marcelrsoub.github.io/react-pic-gallery/)
9
9
 
@@ -23,11 +23,11 @@ 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 **7.7 KB gzipped** (5.55 KB JS + 2.16 KB CSS).
26
+ The package is intentionally lightweight: it has no runtime dependencies and React is a peer dependency. The built JavaScript and CSS together are about **8.5 KB gzipped** (6.13 KB JS + 2.34 KB CSS).
27
27
 
28
28
  ## Why react-pic-gallery
29
29
 
30
- - **Tiny by design** — no runtime dependencies, no context providers, no polyfills; ~7.7 KB gzipped total.
30
+ - **Tiny by design** — no runtime dependencies, no context providers, no polyfills; ~8.5 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.
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ import { GalleryImage, LightboxRenderer } from './types';
3
+ import { UseCarouselNavigationResult } from './useCarouselNavigation';
4
+ export type CarouselProps<T extends GalleryImage = GalleryImage> = {
5
+ images: readonly T[];
6
+ index: number;
7
+ navigation: UseCarouselNavigationResult;
8
+ renderActions?: LightboxRenderer<T>;
9
+ renderCaption?: LightboxRenderer<T>;
10
+ renderControls?: LightboxRenderer<T>;
11
+ showCounter?: boolean;
12
+ showNavigation?: boolean;
13
+ close?: () => void;
14
+ onEmptyAreaClick?: () => void;
15
+ onImageClick?: () => void;
16
+ className?: string;
17
+ };
18
+ export declare function Carousel<T extends GalleryImage>(props: CarouselProps<T>): ReactNode;
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 }: GalleryProps<T>): import('react').JSX.Element;
2
+ declare function GalleryComponent<T extends GalleryImage>({ images, onImageClick, layout, columns, rowHeight, className, style, renderCaption, showCounter, showNavigation }: GalleryProps<T>): import('react').JSX.Element;
3
3
  export declare const Gallery: typeof GalleryComponent;
4
4
  export {};
package/dist/Image.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnimationEventHandler, CSSProperties } from 'react';
1
+ import { AnimationEventHandler, CSSProperties, MouseEventHandler } from 'react';
2
2
  type ImageProps = {
3
3
  src: string;
4
4
  alt: string;
@@ -7,10 +7,11 @@ type ImageProps = {
7
7
  style?: CSSProperties;
8
8
  imageStyle?: CSSProperties;
9
9
  onAnimationEnd?: AnimationEventHandler<HTMLSpanElement>;
10
+ onClick?: MouseEventHandler<HTMLSpanElement>;
10
11
  eager?: boolean;
11
12
  objectFit?: 'cover' | 'contain';
12
13
  width?: number;
13
14
  height?: number;
14
15
  };
15
- export declare function Image({ src, alt, className, imageClassName, style, imageStyle, onAnimationEnd, eager, objectFit, width, height }: ImageProps): import('react').JSX.Element;
16
+ export declare function Image({ src, alt, className, imageClassName, style, imageStyle, onAnimationEnd, onClick, eager, objectFit, width, height }: ImageProps): import('react').JSX.Element;
16
17
  export {};
@@ -2,40 +2,41 @@ import { memo as e, useCallback as t, useEffect as n, useId as r, useLayoutEffec
2
2
  import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
3
3
  import { createPortal as u } from "react-dom";
4
4
  //#region src/lib/Image.tsx
5
- function d({ src: e, alt: t, className: r = "", imageClassName: i = "", style: s, imageStyle: u, onAnimationEnd: d, eager: f = !1, objectFit: p = "cover", width: m, height: h }) {
6
- let g = a(null), [_, v] = o("loading");
5
+ function d({ src: e, alt: t, className: r = "", imageClassName: i = "", style: s, imageStyle: u, onAnimationEnd: d, onClick: f, eager: p = !1, objectFit: m = "cover", width: h, height: g }) {
6
+ let _ = a(null), [v, y] = o("loading");
7
7
  return n(() => {
8
- v("loading");
9
- let e = g.current;
10
- e?.complete && v(e.naturalWidth > 0 ? "loaded" : "error");
8
+ y("loading");
9
+ let e = _.current;
10
+ e?.complete && y(e.naturalWidth > 0 ? "loaded" : "error");
11
11
  }, [e]), /* @__PURE__ */ l("span", {
12
12
  className: `react-pic-gallery__image ${r}`.trim(),
13
13
  style: s,
14
- "aria-busy": _ === "loading",
14
+ "aria-busy": v === "loading",
15
15
  onAnimationEnd: d,
16
+ onClick: f,
16
17
  children: [
17
18
  /* @__PURE__ */ c("img", {
18
- ref: g,
19
+ ref: _,
19
20
  src: e,
20
21
  alt: t,
21
22
  className: `react-pic-gallery__image-element ${i}`.trim(),
22
23
  style: {
23
- objectFit: p,
24
+ objectFit: m,
24
25
  ...u
25
26
  },
26
- loading: f ? "eager" : "lazy",
27
+ loading: p ? "eager" : "lazy",
27
28
  decoding: "async",
28
- width: m,
29
- height: h,
30
- onLoad: () => v("loaded"),
31
- onError: () => v("error")
29
+ width: h,
30
+ height: g,
31
+ onLoad: () => y("loaded"),
32
+ onError: () => y("error")
32
33
  }),
33
- _ === "loading" && /* @__PURE__ */ c("span", {
34
+ v === "loading" && /* @__PURE__ */ c("span", {
34
35
  className: "react-pic-gallery__image-loader",
35
36
  role: "progressbar",
36
37
  "aria-label": "Loading image"
37
38
  }),
38
- _ === "error" && /* @__PURE__ */ c("span", {
39
+ v === "error" && /* @__PURE__ */ c("span", {
39
40
  className: "react-pic-gallery__image-error",
40
41
  role: "status",
41
42
  children: "Unable to load image"
@@ -43,15 +44,137 @@ function d({ src: e, alt: t, className: r = "", imageClassName: i = "", style: s
43
44
  ]
44
45
  });
45
46
  }
47
+ function f(e) {
48
+ return e instanceof Element && e.closest("a[href], button, input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]") !== null;
49
+ }
50
+ function p(e, t) {
51
+ if (!t || window.matchMedia?.("(max-width: 600px), (pointer: coarse)")?.matches !== !0) return null;
52
+ let n = t.getBoundingClientRect();
53
+ if (n.width <= 0) return null;
54
+ let r = (e - n.left) / n.width;
55
+ return r < .3 ? "previous" : r > .7 ? "next" : null;
56
+ }
57
+ function m() {
58
+ return /* @__PURE__ */ c("svg", {
59
+ viewBox: "0 0 24 24",
60
+ "aria-hidden": "true",
61
+ focusable: "false",
62
+ children: /* @__PURE__ */ c("path", { d: "M6 6l12 12M18 6L6 18" })
63
+ });
64
+ }
65
+ function h({ direction: e }) {
66
+ return /* @__PURE__ */ c("svg", {
67
+ viewBox: "0 0 24 24",
68
+ "aria-hidden": "true",
69
+ focusable: "false",
70
+ children: /* @__PURE__ */ c("path", { d: e === "left" ? "M14.5 5l-7 7 7 7" : "M9.5 5l7 7-7 7" })
71
+ });
72
+ }
73
+ function g(...e) {
74
+ return e.filter(Boolean).join(" ");
75
+ }
76
+ //#endregion
77
+ //#region src/lib/Carousel.tsx
78
+ function _(e) {
79
+ let { images: t, index: n, navigation: r, renderActions: i, renderCaption: a, renderControls: o, showCounter: u = !0, showNavigation: f = !0, close: _, onEmptyAreaClick: v, onImageClick: y, className: b } = e, x = t[n], S = {
80
+ image: x,
81
+ index: n,
82
+ count: t.length,
83
+ close: _ ?? (() => {}),
84
+ next: r.next,
85
+ previous: r.previous,
86
+ canGoNext: n < t.length - 1,
87
+ canGoPrevious: n > 0
88
+ }, C = f && t.length > 1, w = f && S.canGoPrevious, T = f && S.canGoNext;
89
+ return /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ l("p", {
90
+ className: "react-pic-gallery__sr-only",
91
+ "aria-live": "polite",
92
+ children: [
93
+ x.alt,
94
+ ", image ",
95
+ n + 1,
96
+ " of ",
97
+ t.length
98
+ ]
99
+ }), /* @__PURE__ */ l("div", {
100
+ className: g("react-pic-gallery__lightbox-content", b),
101
+ children: [
102
+ o ? /* @__PURE__ */ c("div", {
103
+ className: "react-pic-gallery__custom-controls",
104
+ children: o(S)
105
+ }) : /* @__PURE__ */ l("div", {
106
+ className: "react-pic-gallery__toolbar",
107
+ children: [/* @__PURE__ */ l("div", {
108
+ className: "react-pic-gallery__toolbar-actions",
109
+ children: [u && /* @__PURE__ */ l("span", {
110
+ "aria-live": "polite",
111
+ children: [
112
+ n + 1,
113
+ " / ",
114
+ t.length
115
+ ]
116
+ }), i?.(S)]
117
+ }), _ && /* @__PURE__ */ c("button", {
118
+ className: "react-pic-gallery__control",
119
+ type: "button",
120
+ onClick: _,
121
+ "aria-label": "Close image viewer",
122
+ children: /* @__PURE__ */ c(m, {})
123
+ })]
124
+ }),
125
+ /* @__PURE__ */ l("div", {
126
+ className: g("react-pic-gallery__viewport", C ? "react-pic-gallery__viewport--has-navigation" : void 0),
127
+ onMouseDown: (e) => {
128
+ e.target === e.currentTarget && v?.();
129
+ },
130
+ onClick: r.handleViewportClick,
131
+ onTouchCancel: r.handleTouchCancel,
132
+ onTouchEnd: r.handleTouchEnd,
133
+ onTouchStart: r.handleTouchStart,
134
+ children: [
135
+ w && /* @__PURE__ */ c("button", {
136
+ className: "react-pic-gallery__control react-pic-gallery__control--previous",
137
+ type: "button",
138
+ onClick: r.previous,
139
+ "aria-label": "Previous image",
140
+ children: /* @__PURE__ */ c(h, { direction: "left" })
141
+ }),
142
+ /* @__PURE__ */ c(d, {
143
+ src: x.src,
144
+ alt: x.alt,
145
+ eager: !0,
146
+ objectFit: "contain",
147
+ className: g("react-pic-gallery__lightbox-image", r.swipeDirection ? `react-pic-gallery__lightbox-image--swipe-${r.swipeDirection}` : void 0),
148
+ onAnimationEnd: r.handleImageAnimationEnd,
149
+ onClick: y ? (e) => {
150
+ f && t.length > 1 && p(e.clientX, e.currentTarget.parentElement) !== null || y();
151
+ } : void 0
152
+ }, x.src),
153
+ T && /* @__PURE__ */ c("button", {
154
+ className: "react-pic-gallery__control react-pic-gallery__control--next",
155
+ type: "button",
156
+ onClick: r.next,
157
+ "aria-label": "Next image",
158
+ children: /* @__PURE__ */ c(h, { direction: "right" })
159
+ })
160
+ ]
161
+ }),
162
+ (a || x.caption) && /* @__PURE__ */ c("div", {
163
+ className: "react-pic-gallery__caption",
164
+ children: a ? a(S) : x.caption
165
+ })
166
+ ]
167
+ })] });
168
+ }
46
169
  //#endregion
47
170
  //#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;
171
+ var v = 3 / 2;
172
+ function y(e) {
173
+ return Number.isFinite(e.width) && Number.isFinite(e.height) && e.width !== void 0 && e.height !== void 0 && e.width > 0 && e.height > 0 ? e.width / e.height : v;
51
174
  }
52
- function m(e, t, n, r) {
175
+ function b(e, t, n, r) {
53
176
  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;
177
+ let i = Math.max(1, t), a = Math.max(1, n), o = Math.max(0, r), s = e.map(y), c = [], l = 0;
55
178
  for (; l < e.length;) {
56
179
  let t = l + 1, n = i / s[l], r = Math.abs(n - a), u = s[l];
57
180
  for (let c = l + 1; c < e.length; c += 1) {
@@ -70,29 +193,113 @@ function m(e, t, n, r) {
70
193
  return c;
71
194
  }
72
195
  //#endregion
196
+ //#region src/lib/useCarouselNavigation.ts
197
+ function x(e) {
198
+ 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
+ l.current = n, u.current = r, d.current = i, m.current = s, h.current = c;
200
+ let y = t(() => {
201
+ let e = u.current;
202
+ !m.current && e !== null && e > 0 && d.current(e - 1);
203
+ }, []), b = t(() => {
204
+ let e = u.current;
205
+ !m.current && e !== null && e < l.current.length - 1 && d.current(e + 1);
206
+ }, []), x = t(() => {
207
+ let e = u.current;
208
+ m.current || e === null || e === 0 || (v("previous"), d.current(e - 1));
209
+ }, []), S = t(() => {
210
+ let e = u.current;
211
+ m.current || e === null || e >= l.current.length - 1 || (v("next"), d.current(e + 1));
212
+ }, []);
213
+ return {
214
+ previous: y,
215
+ next: b,
216
+ swipeDirection: _,
217
+ handleKeyDown: t((e) => {
218
+ if (e.defaultPrevented) return;
219
+ let t = e.target;
220
+ t instanceof HTMLElement && (t.isContentEditable || t.closest("input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]")) || (e.key === "ArrowLeft" ? (e.preventDefault(), y()) : e.key === "ArrowRight" && (e.preventDefault(), b()));
221
+ }, [b, y]),
222
+ handleTouchStart: t((e) => {
223
+ if (e.touches.length !== 1 || f(e.target)) {
224
+ g.current = null;
225
+ return;
226
+ }
227
+ let t = e.touches[0];
228
+ g.current = {
229
+ x: t.clientX,
230
+ y: t.clientY
231
+ };
232
+ }, []),
233
+ handleTouchEnd: t((e) => {
234
+ let t = g.current;
235
+ if (g.current = null, m.current || !t || e.changedTouches.length === 0) return;
236
+ let n = e.changedTouches[0], r = n.clientX - t.x, i = n.clientY - t.y, a = Math.abs(r);
237
+ a < 48 || a <= Math.abs(i) || (r < 0 ? S() : x());
238
+ }, [S, x]),
239
+ handleTouchCancel: t(() => {
240
+ g.current = null;
241
+ }, []),
242
+ handleImageAnimationEnd: t((e) => {
243
+ e.target === e.currentTarget && v(null);
244
+ }, []),
245
+ handleViewportClick: t((e) => {
246
+ if (!h.current || l.current.length <= 1 || m.current || !(e.target instanceof Element) || e.target.closest("button")) return;
247
+ let t = p(e.clientX, e.currentTarget);
248
+ t === "previous" ? y() : t === "next" && b();
249
+ }, [b, y])
250
+ };
251
+ }
252
+ //#endregion
73
253
  //#region src/lib/Gallery.tsx
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({
254
+ var S = 3, C = 192, w = 12, T = typeof window > "u" ? n : i;
255
+ function E({ images: e, onImageClick: t, renderCaption: n, showCounter: r, showNavigation: i }) {
256
+ let [a, s] = o(0), l = Math.max(0, Math.min(a, e.length - 1)), u = x({
257
+ images: e,
258
+ index: e.length > 0 ? l : null,
259
+ onIndexChange: (e) => {
260
+ e !== null && s(e);
261
+ },
262
+ showNavigation: i
263
+ });
264
+ return e.length === 0 ? null : /* @__PURE__ */ c("div", {
265
+ className: "react-pic-gallery__carousel",
266
+ role: "region",
267
+ "aria-roledescription": "carousel",
268
+ "aria-label": "Image carousel",
269
+ tabIndex: 0,
270
+ onKeyDown: u.handleKeyDown,
271
+ children: /* @__PURE__ */ c(_, {
272
+ images: e,
273
+ index: l,
274
+ navigation: u,
275
+ renderCaption: n,
276
+ showCounter: r,
277
+ showNavigation: i,
278
+ onImageClick: () => t?.(l)
279
+ })
280
+ });
281
+ }
282
+ function D({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeight: u, className: f = "", style: p, renderCaption: m, showCounter: h, showNavigation: g }) {
283
+ let _ = a([]), v = a(null), y = a(null), [x, D] = o(0), [O, k] = o({
77
284
  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 = {
285
+ rowHeight: C,
286
+ gap: w
287
+ }), A = i !== void 0 && Number.isFinite(i) && i >= 1 ? Math.floor(i) : S, j = e.length - 1, M = Math.min(x, Math.max(j, 0)), N = typeof u == "number" ? `${u}px` : u, P = {
81
288
  ...p,
82
- ...r === "grid" && i !== void 0 && Number.isFinite(i) && i >= 1 ? { gridTemplateColumns: `repeat(${E}, minmax(0, 1fr))` } : {},
83
- ...k ? { "--gallery-row-height": k } : {}
289
+ ...r === "grid" && i !== void 0 && Number.isFinite(i) && i >= 1 ? { gridTemplateColumns: `repeat(${A}, minmax(0, 1fr))` } : {},
290
+ ...N ? { "--gallery-row-height": N } : {}
84
291
  };
85
- v(() => {
292
+ T(() => {
86
293
  if (r !== "justified") return;
87
- let e = b.current, t = x.current;
294
+ let e = v.current, t = y.current;
88
295
  if (!e || !t) return;
89
296
  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) => {
297
+ let n = e.getBoundingClientRect().width || e.clientWidth || window.innerWidth || 1024, r = t.getBoundingClientRect(), i = Number.parseFloat(window.getComputedStyle(t).width), a = typeof u == "number" ? u : typeof u == "string" && /^\s*\d+(?:\.\d+)?px\s*$/.test(u) ? Number.parseFloat(u) : C;
298
+ k((e) => {
92
299
  let t = {
93
300
  width: n,
94
301
  rowHeight: r.height || a,
95
- gap: Number.isFinite(i) ? i : r.width || _
302
+ gap: Number.isFinite(i) ? i : r.width || w
96
303
  };
97
304
  return e.width === t.width && e.rowHeight === t.rowHeight && e.gap === t.gap ? e : t;
98
305
  });
@@ -105,17 +312,17 @@ function y({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
105
312
  }, [
106
313
  r,
107
314
  u,
108
- k
315
+ N
109
316
  ]);
110
- let j = t((e) => {
111
- C(e), y.current[e]?.focus();
112
- }, []), M = t((t, n) => {
113
- let i = y.current[t];
317
+ let F = t((e) => {
318
+ D(e), _.current[e]?.focus();
319
+ }, []), I = t((t, n) => {
320
+ let i = _.current[t];
114
321
  if (!i) return null;
115
322
  let a = i.getBoundingClientRect();
116
323
  if (r === "mosaic" && i.classList.contains("react-pic-gallery__tile--mosaic-featured")) {
117
324
  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) => {
325
+ if (_.current.slice(0, e.length).forEach((e, i) => {
119
326
  if (!e || i === t || !r?.contains(e)) return;
120
327
  let a = e.getBoundingClientRect(), u = a.left + a.width / 2, d = a.top + a.height / 2, f = n === "up" ? s - d : d - s;
121
328
  if (f <= 0) return;
@@ -124,52 +331,52 @@ function y({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
124
331
  }), c !== null) return c;
125
332
  }
126
333
  let o = null, s = Infinity;
127
- return y.current.slice(0, e.length).forEach((e, r) => {
334
+ return _.current.slice(0, e.length).forEach((e, r) => {
128
335
  if (!e || r === t) return;
129
336
  let i = e.getBoundingClientRect(), c = n === "up" ? a.top - i.bottom : i.top - a.bottom;
130
337
  if (c < 0) return;
131
338
  let l = c + (i.right < a.left ? a.left - i.right : a.right < i.left ? i.left - a.right : 0);
132
339
  l < s && (s = l, o = r);
133
340
  }), o;
134
- }, [e.length, r]), N = t((e, t) => {
341
+ }, [e.length, r]), L = t((e, t) => {
135
342
  let n = null;
136
343
  if (e.key === "Home") n = 0;
137
- else if (e.key === "End") n = D;
344
+ else if (e.key === "End") n = j;
138
345
  else if (r === "grid") {
139
346
  let r = {
140
347
  ArrowRight: 1,
141
348
  ArrowLeft: -1,
142
- ArrowDown: E,
143
- ArrowUp: -E
349
+ ArrowDown: A,
350
+ ArrowUp: -A
144
351
  }[e.key];
145
352
  if (r !== void 0) {
146
353
  let e = t + r;
147
- e >= 0 && e <= D && (n = e);
354
+ e >= 0 && e <= j && (n = e);
148
355
  }
149
- } else if (e.key === "ArrowRight" && t < D) n = t + 1;
356
+ } else if (e.key === "ArrowRight" && t < j) n = t + 1;
150
357
  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");
358
+ else if (e.key === "ArrowDown") n = I(t, "down");
359
+ else if (e.key === "ArrowUp") n = I(t, "up");
153
360
  else return;
154
- n !== null && n !== t && (e.preventDefault(), j(n));
361
+ n !== null && n !== t && (e.preventDefault(), F(n));
155
362
  }, [
156
- E,
157
- M,
158
- D,
363
+ A,
364
+ I,
365
+ j,
159
366
  r,
160
- j
161
- ]), P = (e, t, r = {}) => /* @__PURE__ */ c("button", {
367
+ F
368
+ ]), R = (e, t, r = {}) => /* @__PURE__ */ c("button", {
162
369
  className: `react-pic-gallery__tile ${r.className ?? ""}`.trim(),
163
370
  type: "button",
164
371
  disabled: !n,
165
- tabIndex: t === O ? 0 : -1,
372
+ tabIndex: t === M ? 0 : -1,
166
373
  ref: (e) => {
167
- y.current[t] = e;
374
+ _.current[t] = e;
168
375
  },
169
376
  style: r.style,
170
- onKeyDown: (e) => N(e, t),
377
+ onKeyDown: (e) => L(e, t),
171
378
  onClick: () => {
172
- C(t), n?.(t);
379
+ D(t), n?.(t);
173
380
  },
174
381
  "aria-label": `Open ${e.alt}`,
175
382
  children: /* @__PURE__ */ c(d, {
@@ -179,27 +386,34 @@ function y({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
179
386
  width: e.width,
180
387
  height: e.height
181
388
  })
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) => {
389
+ }, e.id ?? `${e.src}-${t}`), z = r === "justified" ? b(e, O.width, O.rowHeight, O.gap) : [], B = Array(e.length);
390
+ return z.forEach((e) => {
184
391
  e.indices.forEach((t, n) => {
185
- I[t] = {
392
+ B[t] = {
186
393
  width: `${e.widths[n]}px`,
187
394
  height: `${e.height}px`
188
395
  };
189
396
  });
190
397
  }), /* @__PURE__ */ l("div", {
191
- ref: b,
398
+ ref: v,
192
399
  className: `react-pic-gallery__gallery react-pic-gallery__gallery--${r} ${r === "grid" ? "react-pic-gallery__grid" : ""} ${f}`.trim(),
193
- style: A,
400
+ style: P,
194
401
  "data-testid": r === "grid" ? "react-pic-gallery-grid" : void 0,
195
402
  "data-layout": r,
196
403
  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, {
404
+ r === "grid" && e.map((e, t) => R(e, t)),
405
+ r === "carousel" && /* @__PURE__ */ c(E, {
406
+ images: e,
407
+ onImageClick: n,
408
+ renderCaption: m,
409
+ showCounter: h,
410
+ showNavigation: g
411
+ }),
412
+ r === "justified" && /* @__PURE__ */ l(s, { children: [e.map((e, t) => R(e, t, {
199
413
  className: "react-pic-gallery__tile--justified",
200
- style: I[t]
414
+ style: B[t]
201
415
  })), /* @__PURE__ */ c("span", {
202
- ref: x,
416
+ ref: y,
203
417
  className: "react-pic-gallery__row-height-probe",
204
418
  "aria-hidden": "true"
205
419
  })] }),
@@ -209,251 +423,117 @@ function y({ images: e, onImageClick: n, layout: r = "grid", columns: i, rowHeig
209
423
  className: `react-pic-gallery__mosaic-group react-pic-gallery__mosaic-group--${a} react-pic-gallery__mosaic-group--count-${i.length}`,
210
424
  children: i.map((e, t) => {
211
425
  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}` });
426
+ return R(e, n, { className: `react-pic-gallery__tile--mosaic react-pic-gallery__tile--mosaic-${a}` });
213
427
  })
214
428
  }, `mosaic-${r}`);
215
429
  })
216
430
  ]
217
431
  });
218
432
  }
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;
222
- }
223
- function O(e) {
224
- if (!x.has(e)) {
225
- if (x.size === 0) {
433
+ var O = e(D), k = /* @__PURE__ */ new Set(), A = null;
434
+ function j(e) {
435
+ if (!k.has(e)) {
436
+ if (k.size === 0) {
226
437
  let e = document.body, t = window.innerWidth - document.documentElement.clientWidth, n = Number.parseFloat(window.getComputedStyle(e).paddingRight) || 0;
227
- S = {
438
+ A = {
228
439
  overflow: e.style.overflow,
229
440
  paddingRight: e.style.paddingRight
230
441
  }, e.style.overflow = "hidden", t > 0 && (e.style.paddingRight = `${n + t}px`);
231
442
  }
232
- x.add(e);
443
+ k.add(e);
233
444
  }
234
445
  }
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);
446
+ function M(e) {
447
+ return k.delete(e), k.size > 0 || !A ? !1 : (document.body.style.overflow = A.overflow, document.body.style.paddingRight = A.paddingRight, A = null, !0);
237
448
  }
238
- function A(e) {
449
+ function N(e) {
239
450
  let t = e.trim();
240
451
  return t.endsWith("ms") ? Number.parseFloat(t) : t.endsWith("s") ? Number.parseFloat(t) * 1e3 : 0;
241
452
  }
242
- function j(e) {
453
+ function P(e) {
243
454
  let t = window.getComputedStyle(e), n = t.animationDuration.split(","), r = t.animationDelay.split(",");
244
- return Math.max(...n.map((e, t) => A(e) + A(r[t % r.length] ?? "0s")), 0);
245
- }
246
- function M() {
247
- return /* @__PURE__ */ c("svg", {
248
- viewBox: "0 0 24 24",
249
- "aria-hidden": "true",
250
- focusable: "false",
251
- children: /* @__PURE__ */ c("path", { d: "M6 6l12 12M18 6L6 18" })
252
- });
455
+ return Math.max(...n.map((e, t) => N(e) + N(r[t % r.length] ?? "0s")), 0);
253
456
  }
254
- function N({ direction: e }) {
255
- return /* @__PURE__ */ c("svg", {
256
- viewBox: "0 0 24 24",
257
- "aria-hidden": "true",
258
- focusable: "false",
259
- children: /* @__PURE__ */ c("path", { d: e === "left" ? "M14.5 5l-7 7 7 7" : "M9.5 5l7 7-7 7" })
260
- });
261
- }
262
- function P(...e) {
263
- return e.filter(Boolean).join(" ");
264
- }
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) {
457
+ function F({ images: e, index: i, onIndexChange: s, renderActions: d, renderCaption: f, renderControls: p, showCounter: m = !0, showNavigation: h = !0, className: v }) {
458
+ let y = a(null), b = a(s), S = a(null), C = a(null), w = a(null), T = a(!1), E = a({}), D = r(), [O, k] = o(!1), A = i !== null && i >= 0 && i < e.length ? i : null, N = A === null ? void 0 : e[A];
459
+ b.current = s, w.current = A;
460
+ let F = x({
461
+ images: e,
462
+ index: A,
463
+ onIndexChange: s,
464
+ disabled: O,
465
+ showNavigation: h && !p
466
+ }), I = t(() => {
467
+ S.current !== null && (window.clearTimeout(S.current), S.current = null), C.current = null;
468
+ }, []), L = t(() => {
469
+ let e = w.current;
470
+ if (e !== null && C.current === null) {
273
471
  if (typeof window < "u" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
274
- y.current(null);
472
+ b.current(null);
275
473
  return;
276
474
  }
277
- x.current = { index: e }, C.current = !0, B(!0);
475
+ C.current = { index: e }, k(!0);
278
476
  }
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));
288
- }, []), X = t(() => {
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;
477
+ }, []), R = t((e) => {
478
+ e.preventDefault(), L();
479
+ }, [L]), z = t(() => {
480
+ let e = C.current;
295
481
  if (e) {
296
- if (S.current !== e.index) {
297
- G(), C.current = !1, B(!1);
482
+ if (w.current !== e.index) {
483
+ I(), k(!1);
298
484
  return;
299
485
  }
300
- G(), C.current = !1, B(!1), y.current(null);
486
+ I(), k(!1), b.current(null);
301
487
  }
302
- }, [G]), te = t((e) => {
303
- if (e.defaultPrevented) return;
304
- let t = e.target;
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;
309
- return;
310
- }
311
- let t = e.touches[0];
312
- L.current = {
313
- x: t.clientX,
314
- y: t.clientY
315
- };
316
- }, []), re = t((e) => {
317
- let t = L.current;
318
- if (L.current = null, !t || e.changedTouches.length === 0) return;
319
- let n = e.changedTouches[0], r = n.clientX - t.x, i = n.clientY - t.y, a = Math.abs(r);
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]);
330
- if (n(() => {
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);
488
+ }, [I]), B = t((e) => {
489
+ e.target === e.currentTarget && e.animationName === "react-pic-gallery-fade-out" && z();
490
+ }, [z]), V = t(() => {
491
+ T.current || (I(), k(!1), w.current !== null && b.current(null));
492
+ }, [I]);
493
+ return n(() => {
494
+ let e = C.current;
495
+ e && A !== e.index && (I(), k(!1));
496
+ }, [A, I]), n(() => {
497
+ if (!O || !C.current || !y.current) return;
498
+ let e = P(y.current);
499
+ return S.current = window.setTimeout(z, e), () => {
500
+ S.current !== null && (window.clearTimeout(S.current), S.current = null);
338
501
  };
339
- }, [Z, z]), n(() => {
340
- if (U === null) return;
341
- let e = v.current;
502
+ }, [z, O]), n(() => {
503
+ if (A === null) return;
504
+ let e = y.current;
342
505
  if (!e) return;
343
506
  let t = document.activeElement;
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();
507
+ return j(E.current), e.open || e.showModal(), () => {
508
+ I(), T.current = !0, e.open && e.close(), T.current = !1, M(E.current) && t?.isConnected && t.focus();
346
509
  };
347
- }, [U !== null, G]), U === null || !W || typeof document > "u") return null;
348
- let Q = {
349
- image: W,
350
- index: U,
351
- count: e.length,
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();
359
- };
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,
369
- children: [
370
- /* @__PURE__ */ c("h2", {
371
- className: "react-pic-gallery__sr-only",
372
- id: R,
373
- children: "Image viewer"
374
- }),
375
- /* @__PURE__ */ l("p", {
376
- className: "react-pic-gallery__sr-only",
377
- "aria-live": "polite",
378
- children: [
379
- W.alt,
380
- ", image ",
381
- U + 1,
382
- " of ",
383
- e.length
384
- ]
385
- }),
386
- /* @__PURE__ */ l("div", {
387
- className: "react-pic-gallery__lightbox-content",
388
- children: [
389
- m ? /* @__PURE__ */ c("div", {
390
- className: "react-pic-gallery__custom-controls",
391
- children: m(Q)
392
- }) : /* @__PURE__ */ l("div", {
393
- className: "react-pic-gallery__toolbar",
394
- children: [/* @__PURE__ */ l("div", {
395
- className: "react-pic-gallery__toolbar-actions",
396
- children: [h && /* @__PURE__ */ l("span", {
397
- "aria-live": "polite",
398
- children: [
399
- U + 1,
400
- " / ",
401
- e.length
402
- ]
403
- }), f?.(Q)]
404
- }), /* @__PURE__ */ c("button", {
405
- className: "react-pic-gallery__control",
406
- type: "button",
407
- onClick: K,
408
- "aria-label": "Close image viewer",
409
- children: /* @__PURE__ */ c(M, {})
410
- })]
411
- }),
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,
425
- children: [
426
- ce && /* @__PURE__ */ c("button", {
427
- className: "react-pic-gallery__control react-pic-gallery__control--previous",
428
- type: "button",
429
- onClick: q,
430
- "aria-label": "Previous image",
431
- children: /* @__PURE__ */ c(N, { direction: "left" })
432
- }),
433
- /* @__PURE__ */ c(d, {
434
- src: W.src,
435
- alt: W.alt,
436
- eager: !0,
437
- objectFit: "contain",
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", {
442
- className: "react-pic-gallery__control react-pic-gallery__control--next",
443
- type: "button",
444
- onClick: J,
445
- "aria-label": "Next image",
446
- children: /* @__PURE__ */ c(N, { direction: "right" })
447
- })
448
- ]
449
- }),
450
- (p || W.caption) && /* @__PURE__ */ c("div", {
451
- className: "react-pic-gallery__caption",
452
- children: p ? p(Q) : W.caption
453
- })
454
- ]
455
- })
456
- ]
510
+ }, [A !== null, I]), A === null || !N || typeof document > "u" ? null : u(/* @__PURE__ */ l("dialog", {
511
+ ref: y,
512
+ className: g("react-pic-gallery__lightbox", O ? "react-pic-gallery__lightbox--closing" : void 0, v),
513
+ "aria-labelledby": D,
514
+ onAnimationEnd: B,
515
+ onClose: V,
516
+ onMouseDown: (e) => {
517
+ e.target === e.currentTarget && L();
518
+ },
519
+ onCancel: R,
520
+ onKeyDown: F.handleKeyDown,
521
+ children: [/* @__PURE__ */ c("h2", {
522
+ className: "react-pic-gallery__sr-only",
523
+ id: D,
524
+ children: "Image viewer"
525
+ }), /* @__PURE__ */ c(_, {
526
+ images: e,
527
+ index: A,
528
+ navigation: F,
529
+ renderActions: d,
530
+ renderCaption: f,
531
+ renderControls: p,
532
+ showCounter: m,
533
+ showNavigation: h && !p,
534
+ close: L,
535
+ onEmptyAreaClick: L
536
+ })]
457
537
  }), document.body);
458
538
  }
459
539
  //#endregion
@@ -463,13 +543,16 @@ function I({ images: e, layout: t, columns: n, rowHeight: r, className: i, galle
463
543
  return /* @__PURE__ */ l("div", {
464
544
  className: `react-pic-gallery ${i ?? ""}`.trim(),
465
545
  style: s,
466
- children: [/* @__PURE__ */ c(b, {
546
+ children: [/* @__PURE__ */ c(O, {
467
547
  images: e,
468
548
  layout: t,
469
549
  columns: n,
470
550
  rowHeight: r,
471
551
  className: a,
472
- onImageClick: g
552
+ onImageClick: g,
553
+ renderCaption: d,
554
+ showCounter: p,
555
+ showNavigation: m
473
556
  }), /* @__PURE__ */ c(F, {
474
557
  images: e,
475
558
  index: h,
@@ -483,4 +566,4 @@ function I({ images: e, layout: t, columns: n, rowHeight: r, className: i, galle
483
566
  });
484
567
  }
485
568
  //#endregion
486
- export { b as Gallery, F as Lightbox, I as PicGallery, I as default };
569
+ export { O 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__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}}
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}}
2
2
  /*$vite$:1*/
package/dist/types.d.ts CHANGED
@@ -8,7 +8,7 @@ export type GalleryImage = {
8
8
  width?: number;
9
9
  height?: number;
10
10
  };
11
- export type GalleryLayout = 'grid' | 'justified' | 'mosaic';
11
+ export type GalleryLayout = 'grid' | 'justified' | 'mosaic' | 'carousel';
12
12
  export type LightboxContext<T extends GalleryImage = GalleryImage> = {
13
13
  image: T;
14
14
  index: number;
@@ -26,6 +26,9 @@ export type GalleryProps<T extends GalleryImage = GalleryImage> = {
26
26
  layout?: GalleryLayout;
27
27
  columns?: number;
28
28
  rowHeight?: CSSProperties['height'];
29
+ renderCaption?: LightboxRenderer<T>;
30
+ showCounter?: boolean;
31
+ showNavigation?: boolean;
29
32
  className?: string;
30
33
  style?: CSSProperties;
31
34
  };
@@ -0,0 +1,21 @@
1
+ import { AnimationEvent as ReactAnimationEvent, KeyboardEvent as ReactKeyboardEvent, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
2
+ import { GalleryImage } from './types';
3
+ export type UseCarouselNavigationOptions<T extends GalleryImage> = {
4
+ images: readonly T[];
5
+ index: number | null;
6
+ onIndexChange: (index: number | null) => void;
7
+ disabled?: boolean;
8
+ showNavigation?: boolean;
9
+ };
10
+ export type UseCarouselNavigationResult = {
11
+ previous: () => void;
12
+ next: () => void;
13
+ swipeDirection: 'next' | 'previous' | null;
14
+ handleKeyDown: (event: ReactKeyboardEvent<HTMLElement>) => void;
15
+ handleTouchStart: (event: ReactTouchEvent<HTMLElement>) => void;
16
+ handleTouchEnd: (event: ReactTouchEvent<HTMLElement>) => void;
17
+ handleTouchCancel: () => void;
18
+ handleImageAnimationEnd: (event: ReactAnimationEvent<HTMLElement>) => void;
19
+ handleViewportClick: (event: ReactMouseEvent<HTMLElement>) => void;
20
+ };
21
+ export declare function useCarouselNavigation<T extends GalleryImage>(options: UseCarouselNavigationOptions<T>): UseCarouselNavigationResult;
@@ -0,0 +1,11 @@
1
+ export declare const interactiveControlSelector = "a[href], button, input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]";
2
+ export declare const keyboardInteractiveControlSelector = "input, select, textarea, [role=\"combobox\"], [role=\"menu\"], [role=\"slider\"], [role=\"spinbutton\"], [role=\"tab\"]";
3
+ export declare const edgeTapMediaQuery = "(max-width: 600px), (pointer: coarse)";
4
+ export declare const edgeTapZoneRatio = 0.3;
5
+ export declare function isInteractiveTarget(target: EventTarget | null): boolean;
6
+ export declare function getEdgeTapZone(clientX: number, viewport: HTMLElement | null): "next" | "previous" | null;
7
+ export declare function CloseIcon(): import('react').JSX.Element;
8
+ export declare function ChevronIcon({ direction }: {
9
+ direction: 'left' | 'right';
10
+ }): import('react').JSX.Element;
11
+ export declare function joinClassNames(...names: Array<string | undefined>): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-pic-gallery",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Small, accessible React image gallery and lightbox.",
5
5
  "author": "marcelrsoub",
6
6
  "license": "MIT",