@hoangyell/gallery 1.0.5 → 1.0.7
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 +29 -1
- package/package.json +1 -1
- package/src/Gallery.astro +66 -3
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
## Features
|
|
28
28
|
|
|
29
29
|
- **Pyramid layout** — distribute images across rows with `rows` prop
|
|
30
|
+
- **Two fit modes** — `cover` (default, uniform photo grid) or `contain` (natural aspect, for diagrams and tall portraits)
|
|
30
31
|
- **Lightbox** — click to open, keyboard arrows, swipe, ESC to close
|
|
31
32
|
- **Corner rounding** — only the four outer corners of each group are rounded
|
|
32
33
|
- **Hover zoom** — image scales inside its cell without changing layout
|
|
@@ -79,9 +80,36 @@ import { Gallery } from "@hoangyell/gallery";
|
|
|
79
80
|
| `rows` | `number` | auto (~3 per row) | Number of visual rows |
|
|
80
81
|
| `orientation` | `"landscape" \| "portrait"` | `"landscape"` | Item aspect ratio hint |
|
|
81
82
|
| `scale` | `number` | `1` | Gallery width scale (0–1) |
|
|
82
|
-
| `fit` | `ObjectFit` | `"cover"` | Default CSS object-fit |
|
|
83
|
+
| `fit` | `ObjectFit` | `"cover"` | Default CSS object-fit — see Fit modes below |
|
|
83
84
|
| `class` | `string` | — | Extra wrapper class |
|
|
84
85
|
|
|
86
|
+
## Fit modes
|
|
87
|
+
|
|
88
|
+
The `fit` prop controls **both** the image's `object-fit` **and** the
|
|
89
|
+
container's aspect ratio. Pick the right one for your content:
|
|
90
|
+
|
|
91
|
+
| Mode | What you get | When to use |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `"cover"` (default) | Uniform 4:3 grid (1:1 on mobile). Images are cropped to fill. | Photo galleries — travel, products, faces. The Pinterest aesthetic. |
|
|
94
|
+
| `"contain"` | Container hugs each image's natural aspect ratio. No cropping. | Diagrams, charts, screenshots, comics, tall portraits. |
|
|
95
|
+
|
|
96
|
+
Set it at the gallery level:
|
|
97
|
+
|
|
98
|
+
```mdx
|
|
99
|
+
<Gallery items={["/diagram.png"]} rows={1} fit="contain" />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Or per-item (mix and match in the same gallery):
|
|
103
|
+
|
|
104
|
+
```mdx
|
|
105
|
+
<Gallery
|
|
106
|
+
items={[
|
|
107
|
+
"/cover-photo.jpg",
|
|
108
|
+
{ src: "/architecture-diagram.png", fit: "contain" },
|
|
109
|
+
]}
|
|
110
|
+
/>
|
|
111
|
+
```
|
|
112
|
+
|
|
85
113
|
## Dark mode
|
|
86
114
|
|
|
87
115
|
The component auto-themes via three mechanisms (last one wins):
|
package/package.json
CHANGED
package/src/Gallery.astro
CHANGED
|
@@ -100,6 +100,7 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
100
100
|
class={`hy-gallery ${className}`}
|
|
101
101
|
data-hy-gallery
|
|
102
102
|
data-orientation={orientation}
|
|
103
|
+
data-fit={fit}
|
|
103
104
|
style={scale !== 1 ? `width: ${scale * 100}%; margin: 2rem auto;` : undefined}
|
|
104
105
|
>
|
|
105
106
|
{groupedItems.map((group, rowIdx) => {
|
|
@@ -124,6 +125,7 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
124
125
|
class={`hy-gallery-item ${cornerClasses}`}
|
|
125
126
|
data-zoom-src={item.src}
|
|
126
127
|
data-alt={item.alt}
|
|
128
|
+
data-fit={item.fit}
|
|
127
129
|
aria-label={
|
|
128
130
|
item.alt ? `View full size: ${item.alt}` : "View full size image"
|
|
129
131
|
}
|
|
@@ -263,6 +265,16 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
|
|
268
|
+
/* fit="contain" — show the whole image at its natural aspect ratio.
|
|
269
|
+
Drops the forced 4:3 / 1:1 container so diagrams, charts, screenshots,
|
|
270
|
+
and tall portraits render fully without cropping or letterboxing.
|
|
271
|
+
Applied either at the wrapper (global) or per-item via data-fit. */
|
|
272
|
+
.hy-gallery[data-fit="contain"] .hy-gallery-item,
|
|
273
|
+
.hy-gallery .hy-gallery-item[data-fit="contain"] {
|
|
274
|
+
aspect-ratio: auto;
|
|
275
|
+
background: transparent;
|
|
276
|
+
}
|
|
277
|
+
|
|
266
278
|
.hy-gallery-img {
|
|
267
279
|
position: absolute;
|
|
268
280
|
inset: 0;
|
|
@@ -279,6 +291,16 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
279
291
|
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
280
292
|
}
|
|
281
293
|
|
|
294
|
+
/* In contain mode the image is the primary content — let it drive height
|
|
295
|
+
instead of being absolutely positioned inside a fixed-aspect box. */
|
|
296
|
+
.hy-gallery[data-fit="contain"] .hy-gallery-img,
|
|
297
|
+
.hy-gallery .hy-gallery-item[data-fit="contain"] .hy-gallery-img {
|
|
298
|
+
position: static;
|
|
299
|
+
width: 100%;
|
|
300
|
+
height: auto;
|
|
301
|
+
inset: auto;
|
|
302
|
+
}
|
|
303
|
+
|
|
282
304
|
.hy-gallery-item:hover .hy-gallery-img,
|
|
283
305
|
.hy-gallery-item:focus-visible .hy-gallery-img {
|
|
284
306
|
will-change: transform;
|
|
@@ -478,6 +500,12 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
478
500
|
currentIndex: number;
|
|
479
501
|
touchStartX: number;
|
|
480
502
|
touchStartY: number;
|
|
503
|
+
lockedScrollY: number;
|
|
504
|
+
previousBodyOverflow: string;
|
|
505
|
+
previousBodyPosition: string;
|
|
506
|
+
previousBodyTop: string;
|
|
507
|
+
previousBodyWidth: string;
|
|
508
|
+
previousHtmlOverflow: string;
|
|
481
509
|
|
|
482
510
|
constructor(el: HTMLElement) {
|
|
483
511
|
this.gallery = el;
|
|
@@ -492,6 +520,12 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
492
520
|
this.currentIndex = -1;
|
|
493
521
|
this.touchStartX = 0;
|
|
494
522
|
this.touchStartY = 0;
|
|
523
|
+
this.lockedScrollY = -1;
|
|
524
|
+
this.previousBodyOverflow = "";
|
|
525
|
+
this.previousBodyPosition = "";
|
|
526
|
+
this.previousBodyTop = "";
|
|
527
|
+
this.previousBodyWidth = "";
|
|
528
|
+
this.previousHtmlOverflow = "";
|
|
495
529
|
|
|
496
530
|
this.bind();
|
|
497
531
|
document.addEventListener("astro:before-swap", this.forceClose);
|
|
@@ -517,10 +551,10 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
517
551
|
|
|
518
552
|
document.removeEventListener("keydown", this.handleKey);
|
|
519
553
|
document.removeEventListener("gesturestart", this.preventZoom);
|
|
520
|
-
document.body.style.overflow = "";
|
|
521
554
|
const main = document.querySelector("main");
|
|
522
555
|
if (main) main.removeAttribute("inert");
|
|
523
556
|
}
|
|
557
|
+
this.unlockBackgroundScroll();
|
|
524
558
|
document.removeEventListener("astro:before-swap", this.forceClose);
|
|
525
559
|
};
|
|
526
560
|
|
|
@@ -539,7 +573,7 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
539
573
|
this.preload(index + 1);
|
|
540
574
|
|
|
541
575
|
document.body.appendChild(this.overlay);
|
|
542
|
-
|
|
576
|
+
this.lockBackgroundScroll();
|
|
543
577
|
const main = document.querySelector("main");
|
|
544
578
|
if (main) main.setAttribute("inert", "");
|
|
545
579
|
this.overlay.focus();
|
|
@@ -628,7 +662,7 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
628
662
|
this.imgEl = null;
|
|
629
663
|
this.counterEl = null;
|
|
630
664
|
|
|
631
|
-
|
|
665
|
+
this.unlockBackgroundScroll();
|
|
632
666
|
const main = document.querySelector("main");
|
|
633
667
|
if (main) main.removeAttribute("inert");
|
|
634
668
|
|
|
@@ -641,6 +675,35 @@ if (resolvedRows <= 1 || galleryItems.length <= 1) {
|
|
|
641
675
|
}, 300);
|
|
642
676
|
};
|
|
643
677
|
|
|
678
|
+
lockBackgroundScroll = () => {
|
|
679
|
+
if (this.lockedScrollY >= 0) return;
|
|
680
|
+
|
|
681
|
+
this.lockedScrollY = window.scrollY;
|
|
682
|
+
this.previousBodyOverflow = document.body.style.overflow;
|
|
683
|
+
this.previousBodyPosition = document.body.style.position;
|
|
684
|
+
this.previousBodyTop = document.body.style.top;
|
|
685
|
+
this.previousBodyWidth = document.body.style.width;
|
|
686
|
+
this.previousHtmlOverflow = document.documentElement.style.overflow;
|
|
687
|
+
|
|
688
|
+
document.documentElement.style.overflow = "hidden";
|
|
689
|
+
document.body.style.overflow = "hidden";
|
|
690
|
+
document.body.style.position = "fixed";
|
|
691
|
+
document.body.style.top = `-${this.lockedScrollY}px`;
|
|
692
|
+
document.body.style.width = "100%";
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
unlockBackgroundScroll = () => {
|
|
696
|
+
if (this.lockedScrollY < 0) return;
|
|
697
|
+
|
|
698
|
+
document.documentElement.style.overflow = this.previousHtmlOverflow;
|
|
699
|
+
document.body.style.overflow = this.previousBodyOverflow;
|
|
700
|
+
document.body.style.position = this.previousBodyPosition;
|
|
701
|
+
document.body.style.top = this.previousBodyTop;
|
|
702
|
+
document.body.style.width = this.previousBodyWidth;
|
|
703
|
+
window.scrollTo(0, this.lockedScrollY);
|
|
704
|
+
this.lockedScrollY = -1;
|
|
705
|
+
};
|
|
706
|
+
|
|
644
707
|
navigate = (dir: number) => {
|
|
645
708
|
const next = this.currentIndex + dir;
|
|
646
709
|
if (next < 0 || next >= this.items.length) return;
|