@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoangyell/gallery",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Astro image gallery with lightbox, pyramid multi-row layout, and accessible controls.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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
- document.body.style.overflow = "hidden";
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
- document.body.style.overflow = "";
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;