The package is built from two primitives, streams and actions. You can use them for environments of your own.
Streams
\begin{webstream}[key=value]{kind} makes what is typeset inside it a stream: a separate run of paragraphs and displays with a name, its kind, placed where the environment stood. Streams nest, and a footnote is a stream too. On its way to the reader a stream passes four stages:
1. TEX typesets the body as usual; every node inside carries the stream’s number in a LuaTeX attribute, which changes nothing about the typesetting.
2. The serializer collects the paragraphs and displays of that number into a stream of that kind, and leaves a placeholder in the flow.
3. The viewer lays the stream out inside <div class="latex-stream" data-kind="kind">, re-broken at the width of that element, with every parameter as a data-key attribute.
4. The page decides what the kind looks like, with CSS, and what it does, with a few lines of JavaScript.
Actions
\webaction{action}{text} makes the text a control: clicking it sends action to the page as a DOM event that bubbles out through the streams around it. The accordion’s links are actions.
Example: a warning
Step 1: the environment. Name a kind and open a stream of it, in the preamble.
Its [key=value] parameters reach the page as they are written (a colour may be #c2410c). \NewWebAside does the same for a command whose text stands out of the flow, in the margin or a popover. Step 2: the look. Style the stream’s element like any other. Padding narrows the measure: the text inside is broken to fit what is left.
Warning. This paragraph was written inside webwarning, a kind this page defines for itself. In a PDF it is an ordinary paragraph.
\begin{webwarning}\noindent\textbf{Warning.} This paragraph was written inside
\texttt{webwarning}, a kind this page defines for itself. In a PDF it is an
ordinary paragraph.
\end{webwarning}
Step 3: the behaviour, when a kind needs one. The companion package draws a kind with a component, in the element the stream has in the flow. <Typeset/> is the stream’s text, laid out at the width it is given and spaced from the text around it as TEX spaced it; the element grows and shrinks with what the component draws, and the text after it follows. State kept with useInstanceState outlives redrawing. The built-in hint is this, plus keyboard handling and the look:
(The real definitions add defaults and the print forms.) Its component draws each pane’s text, showing one, and handles the actions of verb pane pressed in them: useAction gets them however deep in the panes the link stands, and one accordion inside another handles its own.
define('accordion',({instance,attrs})=>{constpanes=instance.children.filter(c=>c.kind==='pane');// find: a pane by name, number, next, prev, first or last
constopen=useInstanceState('pane',()=>find(panes,attrs.initial||'1'));useAction('pane',({arg})=>{open.value=find(panes,arg,open.value);});returnhtml`${panes.map((p,i)=>html`
<div class="rtx-pane" data-state=${i===open.value?'open':'closed'}>
<${Typeset} of=${p} edge=${i===open.value?'both':undefined} />
</div>`)}`;});
The package’s own adds the animation, the keyboard and print (src/companion/src/kinds/accordion.tsx).