Skip to main content

max / makenotwork

1.9 KB · 52 lines History Blame Raw
1 // <mnw-carousel> — click-through carousel over server-rendered frames, ported
2 // from static/carousel.js. Progressive enhancement: until the element upgrades,
3 // the first frame shows and the controls are inert (CSS gates on [data-ready]).
4
5 import { MnwElement, define } from './base.ts';
6 import { wrapIndex } from './carousel.logic.ts';
7
8 class Carousel extends MnwElement {
9 protected init(): void {
10 const frames = Array.from(this.querySelectorAll<HTMLElement>('.carousel-frame'));
11 if (frames.length < 2) return; // nothing to navigate
12
13 const dots = Array.from(this.querySelectorAll<HTMLElement>('[data-carousel-dot]'));
14 let index = frames.findIndex((f) => f.classList.contains('is-active'));
15 if (index < 0) index = 0;
16
17 const show = (next: number): void => {
18 index = wrapIndex(next, frames.length);
19 frames.forEach((f, i) => {
20 const active = i === index;
21 f.classList.toggle('is-active', active);
22 if (active) f.removeAttribute('aria-hidden');
23 else f.setAttribute('aria-hidden', 'true');
24 });
25 dots.forEach((d, i) => {
26 const active = i === index;
27 d.classList.toggle('is-active', active);
28 d.setAttribute('aria-selected', active ? 'true' : 'false');
29 });
30 };
31
32 this.querySelector('[data-carousel-prev]')?.addEventListener('click', () => show(index - 1));
33 this.querySelector('[data-carousel-next]')?.addEventListener('click', () => show(index + 1));
34 dots.forEach((d) => {
35 d.addEventListener('click', () => show(parseInt(d.getAttribute('data-carousel-dot') ?? '', 10) || 0));
36 });
37 this.addEventListener('keydown', (e) => {
38 if (e.key === 'ArrowLeft') {
39 show(index - 1);
40 e.preventDefault();
41 } else if (e.key === 'ArrowRight') {
42 show(index + 1);
43 e.preventDefault();
44 }
45 });
46
47 this.setAttribute('data-ready', ''); // CSS hook: controls become live
48 }
49 }
50
51 define('mnw-carousel', Carousel);
52