generated from boboko/starter
117 lines
3.5 KiB
JavaScript
117 lines
3.5 KiB
JavaScript
import { Controller } from "@hotwired/stimulus";
|
|
|
|
const SCROLL_STEP = 1;
|
|
|
|
export default class extends Controller {
|
|
static targets = ["main", "thumb", "track", "lightbox", "lightboxImage", "lightboxCounter"];
|
|
|
|
connect() {
|
|
this.offset = 0;
|
|
this.lightboxIndex = 0;
|
|
this.#syncHeight();
|
|
|
|
this.mainTarget.addEventListener("load", () => this.#syncHeight());
|
|
|
|
this._onKeydown = this.#handleKeydown.bind(this);
|
|
document.addEventListener("keydown", this._onKeydown);
|
|
}
|
|
|
|
disconnect() {
|
|
document.removeEventListener("keydown", this._onKeydown);
|
|
}
|
|
|
|
select(event) {
|
|
const btn = event.currentTarget;
|
|
this.#setThumb(btn);
|
|
this.lightboxIndex = this.thumbTargets.indexOf(btn);
|
|
}
|
|
|
|
scrollUp() {
|
|
this.offset = Math.max(0, this.offset - SCROLL_STEP);
|
|
this.#applyScroll();
|
|
}
|
|
|
|
scrollDown() {
|
|
this.offset = Math.min(this.thumbTargets.length - 1, this.offset + SCROLL_STEP);
|
|
this.#applyScroll();
|
|
}
|
|
|
|
closeLightboxOnBackdrop(event) {
|
|
// Only close if clicking directly on the backdrop (the popover div itself),
|
|
// not on the image, arrows, close button, or counter
|
|
if (event.target === this.lightboxTarget) {
|
|
this.lightboxTarget.hidePopover();
|
|
}
|
|
}
|
|
|
|
noop(event) {
|
|
event.stopPropagation();
|
|
}
|
|
|
|
openLightbox() {
|
|
this.#updateLightbox(this.lightboxIndex);
|
|
this.lightboxTarget.showPopover();
|
|
}
|
|
|
|
prevImage() {
|
|
const next = (this.lightboxIndex - 1 + this.thumbTargets.length) % this.thumbTargets.length;
|
|
this.#updateLightbox(next);
|
|
}
|
|
|
|
nextImage() {
|
|
const next = (this.lightboxIndex + 1) % this.thumbTargets.length;
|
|
this.#updateLightbox(next);
|
|
}
|
|
|
|
// ── Private ────────────────────────────────────────────────────
|
|
|
|
#updateLightbox(index) {
|
|
this.lightboxIndex = index;
|
|
const thumb = this.thumbTargets[index];
|
|
if (!thumb) return;
|
|
|
|
this.lightboxImageTarget.src = thumb.dataset.src;
|
|
this.lightboxImageTarget.alt = thumb.dataset.alt;
|
|
this.lightboxCounterTarget.textContent =
|
|
`${index + 1} of ${this.thumbTargets.length}`;
|
|
}
|
|
|
|
#setThumb(btn) {
|
|
const src = btn.dataset.src;
|
|
const alt = btn.dataset.alt;
|
|
|
|
this.mainTarget.src = src;
|
|
this.mainTarget.alt = alt;
|
|
|
|
this.thumbTargets.forEach((t) => {
|
|
const active = t === btn;
|
|
t.classList.toggle("border-2", active);
|
|
t.classList.toggle("border-1", !active);
|
|
t.setAttribute("aria-pressed", String(active));
|
|
});
|
|
}
|
|
|
|
#applyScroll() {
|
|
if (!this.thumbTargets.length) return;
|
|
const thumbHeight = this.thumbTargets[0].offsetHeight;
|
|
const gap = 16; // gap-4 = 16px
|
|
this.trackTarget.scrollTop = this.offset * (thumbHeight + gap);
|
|
}
|
|
|
|
#syncHeight() {
|
|
const h = this.mainTarget.offsetHeight;
|
|
if (h > 0) {
|
|
this.trackTarget.style.maxHeight = h + "px";
|
|
}
|
|
}
|
|
|
|
#handleKeydown(e) {
|
|
if (!this.hasLightboxTarget) return;
|
|
if (!this.lightboxTarget.matches(":popover-open")) return;
|
|
|
|
if (e.key === "ArrowLeft") { e.preventDefault(); this.prevImage(); }
|
|
if (e.key === "ArrowRight") { e.preventDefault(); this.nextImage(); }
|
|
if (e.key === "Escape") { this.lightboxTarget.hidePopover(); }
|
|
}
|
|
}
|