// — click-through carousel over server-rendered frames, ported // from static/carousel.js. Progressive enhancement: until the element upgrades, // the first frame shows and the controls are inert (CSS gates on [data-ready]). import { MnwElement, define } from './base.ts'; import { wrapIndex } from './carousel.logic.ts'; class Carousel extends MnwElement { protected init(): void { const frames = Array.from(this.querySelectorAll('.carousel-frame')); if (frames.length < 2) return; // nothing to navigate const dots = Array.from(this.querySelectorAll('[data-carousel-dot]')); let index = frames.findIndex((f) => f.classList.contains('is-active')); if (index < 0) index = 0; const show = (next: number): void => { index = wrapIndex(next, frames.length); frames.forEach((f, i) => { const active = i === index; f.classList.toggle('is-active', active); if (active) f.removeAttribute('aria-hidden'); else f.setAttribute('aria-hidden', 'true'); }); dots.forEach((d, i) => { const active = i === index; d.classList.toggle('is-active', active); d.setAttribute('aria-selected', active ? 'true' : 'false'); }); }; this.querySelector('[data-carousel-prev]')?.addEventListener('click', () => show(index - 1)); this.querySelector('[data-carousel-next]')?.addEventListener('click', () => show(index + 1)); dots.forEach((d) => { d.addEventListener('click', () => show(parseInt(d.getAttribute('data-carousel-dot') ?? '', 10) || 0)); }); this.addEventListener('keydown', (e) => { if (e.key === 'ArrowLeft') { show(index - 1); e.preventDefault(); } else if (e.key === 'ArrowRight') { show(index + 1); e.preventDefault(); } }); this.setAttribute('data-ready', ''); // CSS hook: controls become live } } define('mnw-carousel', Carousel);