Skip to main content

max / makenotwork

3.1 KB · 63 lines History Blame Raw
1 // <div data-showing="one">, click-through carousel over a described gallery.
2 //
3 // The widget tier's browser half, and it is smaller than it was. The server no
4 // longer renders a carousel and no longer renders its chrome either: it renders
5 // an ordered set of pictures that says one of them is showing, and quasi-webview
6 // derives prev/position/next from that fact alone. So this file stopped
7 // injecting buttons and stopped building a dot strip. What is left is the one
8 // thing a description cannot carry: which frame the reader has moved to since
9 // the page arrived.
10 //
11 // NOTHING HERE KNOWS WHAT A CAROUSEL IS. The hook is `data-showing`, not
12 // `data-widget="carousel"`, and that is deliberate: a script that matched on the
13 // widget's name would have to be written again for the next assembly showing one
14 // child at a time. This binds every one of them, including ones that do not
15 // exist yet.
16 //
17 // PROGRESSIVE ENHANCEMENT. The controls are on the page but makeover keeps
18 // `.showing` hidden until `data-ready` is set, so a reader with no script is
19 // never offered a button that does nothing. `no-js.css`, loaded from a
20 // <noscript> in the partial, opens the frame stack out for them instead.
21
22 import { MnwElement, define } from './base.ts';
23 import { bindShowing } from '../core/showing.ts';
24
25 class Carousel extends MnwElement {
26 protected init(): void {
27 // The element wraps the described region rather than being it. A custom
28 // element is what gets upgraded automatically on an htmx swap, which is the
29 // whole reason islands are custom elements; the description emits a plain
30 // `<div>` because it has no idea this host prefixes its tags with `mnw-`.
31 // So the template puts one around the other and each half stays honest.
32 const region = this.querySelector<HTMLElement>('[data-showing]');
33 if (!region) return;
34
35 // Moving between frames is shared with every other region that shows one
36 // child at a time, `core/showing.ts`: a gallery and a tab strip are the
37 // same fact to the renderer, so they were the same loop written twice here
38 // until the library strip needed the second copy.
39 const showing = bindShowing(region);
40 if (!showing) return; // nothing to navigate; leave the stack alone
41
42 // The group semantics the template used to carry. They belong with the
43 // behaviour: a stack of pictures is not a carousel until this runs, and
44 // announcing one before then describes something the reader cannot use.
45 region.setAttribute('role', 'group');
46 region.setAttribute('aria-roledescription', 'carousel');
47 region.setAttribute('aria-label', 'Screenshots');
48 region.tabIndex = 0;
49
50 const frames = region.querySelectorAll<HTMLElement>(':scope > .showing-frame');
51 frames.forEach((f, i) => {
52 f.setAttribute('role', 'group');
53 f.setAttribute('aria-roledescription', 'slide');
54 f.setAttribute('aria-label', `${i + 1} of ${showing.count()}`);
55 });
56
57 showing.show(showing.at());
58 region.setAttribute('data-ready', ''); // reveals the row, collapses the stack
59 }
60 }
61
62 define('mnw-carousel', Carousel);
63