# @aweftjs/dom

The DOM binding: state from `@aweftjs/core` onto real nodes, with no virtual tree. `h` makes
an element the moment it runs, a slot binds straight to a node, and a list binds straight to
the commits its array produces. The same page mounts in a browser, renders to markup with no
browser, and takes over that markup in place.

## Quickstart

```ts
import { mutable } from '@aweftjs/core';
import { html, mount } from '@aweftjs/dom';

const count = mutable(0);

mount(document.body, html`
	<button $onclick=${() => count.set(count.get() + 1)}>
		Button clicked ${count} times
	</button>
	<button $onclick=${() => count.set(0)}>Reset</button>
`);
```

`mount` returns the function that unmounts. Everything `mount` takes:

| item | what happens |
|---|---|
| a string, number or boolean | a text node |
| `null` | nothing. `undefined` is refused, so a typo cannot render as silence |
| a node | inserted as it is |
| `h()` output, `html` output | the element, with its reactive parts bound |
| an array or any iterable | each item in order |
| a document array (`createArray`), a mutable array (`mutableArray`) | a list that follows every edit |
| a scope, a cell or a derived value | its value, replaced in place as it changes |
| a component's mounter (`h(Component)`) | the component's result |

**An item may be mounted, unmounted and mounted again**, which is what a slot component whose
branches are written out as markup hands the binding: the branch is built once and mounted every
time it comes back. Unmounting takes what the mount put in back out, so the element is left
holding the static nodes `h` built and the next mount renders once (design 204).

**The one exception is a list.** A list edit takes a row's nodes out itself and then tells the row
its nodes are already gone, which is what keeps a list edit to one write. So an element the page
holds, put into a list, removed by an edit and put back, keeps what its last mount left in it, and
the second mount renders its reactive parts on top: `<em>one</em>` comes back as `<em>oneone</em>`.
Build list items per row, with a component under `each`, and there is no element to hand back.

## Elements: `h` and `html`

`h(tag, props, ...children)` is what `html` compiles to at runtime, and what a JSX transform
targets. A bare prop name is an attribute; `$name` is a property on the element, which is
how `value`, `checked`, `textContent`, and every event handler (`$onclick`) are set. `$style`
takes an object and writes each key onto `element.style`.

```ts
h('input', { type: 'text', $value: draft, $oninput: (e) => draft.set(e.target.value) });
h('div', { class: active.bool('on', 'off'), $style: { color, fontWeight: 'bold' } }, 'label');
```

Any prop or child may be a scope, cell or derived value, and follows it. A `null`, `undefined`
or `false` attribute value removes the attribute; `true` sets it empty. An element with no
reactive parts is returned as itself, so `const box = h('div', { class: 'box' })` is a node
you can hand to the browser.

`html` is a tag for template literals. A tag may be an expression (`<${Component}>`), `</>`
closes the innermost open element, `<!-- ... -->` is a comment and goes, and `${value}` between
tags is a child. Whitespace works as in JSX: lines are trimmed, blank lines go, and a line break
inside text is one space. Close void elements yourself (`<br/>`).

Inside a tag, these are all the forms there are:

| written | means |
| --- | --- |
| `hidden` | the attribute set to `true` |
| `id=plain` | the unquoted text up to the next space or `>` |
| `class="row ${tone}"` | the quoted parts as one value, derived when a cell is among them |
| `title=${t}`, `$onclick=${fn}` | the expression itself, followed if it is a cell |
| `=${props}` or `${props}` | a spread: every key of the object becomes a prop |

A spread is the one form with two spellings. `=${props}` is the explicit one; a hole on its own,
with no name in front of it, means the same thing. Either way it must be an object, and a page
that hands it anything else gets `a spread in a tag must be an object` in development.

`htm(h, { join })` binds the parser to another `h`; `join` says how a quoted attribute of
several parts becomes one value, by default the parts concatenated, and derived when a cell
is among them.

## Components

A component is a function called once, with its props, when it mounts:

```ts
const Timer = ({ children }, cleanup, mounted, pending) => {
	const seconds = timer(1000);
	cleanup(() => console.log('gone'));
	mounted(() => console.log('in the document, descendants included'));
	return h('p', {}, 'up for ', seconds, 's ', ...children);
};

mount(document.body, h(Timer, {}, 'and counting'));
```

`props.children` is always an array. `cleanup` takes functions to run after the component
and everything below it is unmounted; calling it after that runs them at once. `mounted`
takes functions to run once the component's nodes are in the document, children before
parents, and may only be called while mounting. `pending` takes a promise `render` will wait
for (below). A component may return anything `mount` takes, including another component or
a mounter of its own.

The body runs from a queue the mount drains once its own walk is done, never from inside a
reconciliation, so a component that edits state while mounting cannot corrupt the tree. A
body that throws names the component in the error, the rest of the queue still runs, and the
error reaches whoever made the change.

An assert reports a defect and does not recover: what was inserted before the throw stays in
the page, the caller of `mount` gets no remove function, and an array edited from the
delivery that threw keeps its edit. Fix the defect rather than catching the assert.

**`each`** mounts a component once per item, the item in `props.each`:

```ts
const Todo = ({ each: todo }) => h('li', { class: observer(todo).path('done').bool('done', '') },
	observer(todo).path('title'));

h('ul', {}, h(Todo, { each: state.todos }));
```

**A component under `each` renders the same node shape on every call.** The first row is
built and remembered; every row after it is a clone of that one with this row's values
written in, which is what makes a long list cheap. So the tags, their order, the prop keys
and which children are present have to be the same for every item. What changes per row is
values: text, attribute and property values, and what a scope or cell drives.

**A function is a value like any other, and it belongs to its row.** Each row's body is called
with a props object of its own, so a handler written in the body reads that row's `props.each`
whenever it fires, whether it goes on a nested component or straight onto an element as
`$onclick` (design 205).

Most of this is unchecked, and breaking it is quiet. A tag that varies keeps the first row's
tag, and a prop key or a child that only some rows have is dropped. Branch on the item inside a
value, not around the markup:

```ts
// Good: one shape, a value that varies.
({ each: todo }) => h('li', { class: observer(todo).path('done').bool('done', '') }, todo.title);

// Wrong: two shapes from one component. Every row takes the first row's tag, silently.
({ each: todo }) => (todo.done ? h('s', {}, todo.title) : h('li', {}, todo.title));
```

The prop keys have to match too, including the keys inside a `$style` object: a row that names
fewer of them than the first row did keeps the first row's, because a clone carries the style
it was cloned from. And what `h` returns inside such a body is only good for handing to another
`h` or returning; a body that keeps it, reads a property off it or mounts into it is not using
`h` to build the row, and the list quietly stops cloning when it can tell.

Two shapes means two components, each with its own `each`, or one component whose body mounts
the varying part as a child. Two breaks are caught. A row whose body returned something other
than the element `h` built for it, such as text or a pair, runs its body a second time, and the
list stops cloning from then on: its rows are correct and slower. A row that called `h` a
different number of times than the first row asserts, naming both counts, because its values no
longer line up with anything.

## Lists

A document array or a mutable array delivers every edit as the edit: a push inserts one
node, a splice removes one, a swap inside one `atomic` block moves the two row nodes
themselves, so an input inside keeps what it holds. Emptying the whole list when it is the
parent's whole content is one write. A plain array in a cell is diffed by reference when the
cell is replaced: rows that are the same value stay, others are made or removed.

```ts
state.todos.push(createObject({ title: 'ship', done: false }));   // one insert
atomic(() => { const t = todos[0]; todos[0] = todos[1]; todos[1] = t; }); // two moves
```

Register a scope where you read it, on the row rather than the document root: `observer(row)
.path('title')` costs nothing on writes elsewhere.

## Static render

```ts
const markup = await render(h(App, { url }));
```

`render` mounts the item into a light document this package ships, waits until nothing a
component declared `pending` is still pending, and returns the markup. Every dynamic part (a
scope, a component, each item of a list) is bracketed with `<!--[-->` and `<!--]-->`, which is
how `hydrate` finds it. No doctype is added. Inside `render`, `h` and `createElement` make
nodes in the light document, so a component works unchanged. `render(item, { context })`
hands `context` to every mounter below, as `mount` does.

A component that fetches declares it: `pending(fetch(url).then((r) => r.json()).then(fill))`.
A component that forgets renders its loading state into the page, which is visible. The
continuation runs outside the mount: set state in it and let the component's bindings build
the nodes, because `h` called from the continuation itself has no render document to make
them in.

## Hydration

```ts
hydrate(document.body, h(App, { url: location.pathname }));
hydrate(document.body, () => h('main', {}, 'ready'));
```

`hydrate` takes what makes the item: a component call, `h(App, props)`, or a function that
makes it, which is mounted as a component with no props. It cannot take the element itself.
Only a mount records which nodes the binding made, so an element built before the call was
made outside every mount and has nothing recorded about it; handed straight in, or returned by
the maker, it is refused, and the refusal names the maker form as the fix.

`hydrate` mounts that item over markup `render` wrote and adopts the server's nodes in
place: a matching element keeps its identity and gains the properties and listeners the
client gives it, text is adopted, lists keep working. Nothing is wiped and nothing flashes.

A mismatch between the markup and what the client renders is a defect: it asserts in
development, and in a release build the region that differs is replaced with what the
client built while the rest is kept. Only the top-level item is refused for being built
outside the mount. Deeper in the tree a node the application made outside the mount, by `h`
or by the page, is inserted as it is and stands in for the server's node of the same tag,
which is how a component keeps a canvas or a map it owns. Markup must parse back into the
tree `render` wrote: write `tbody` yourself, and avoid whitespace-only text where the browser
drops it.

**`hydrate` waits for what the page is still loading.** The pairing walk stays open until every
promise a component declared `pending` during the hydration has settled, so a page whose content
arrives one microtask later still claims the markup the server wrote for it. Until then nothing
has been checked and nothing the server sent has been taken away: what is on screen is the
server's markup, untouched.

```ts
const page = hydrate(document.body, h(App, { url: location.pathname }));
await page.ready;                       // every pending load settled, the markup checked
```

`ready` is on the remove function `hydrate` answers, and it resolves for a page with nothing
pending before the call has even returned. It never rejects: a mismatch found after the wait is
thrown on a fresh task, where the host reports it. Removing the hydration while a load is in
flight closes the walk and checks nothing, because the page it was pairing has gone.

A component that waits with `pending` and shows a loading state runs again on the client, and
showing that state over the server's finished markup is a mismatch, whatever the wait does after.
Either render what you know (give the resolved value as a prop from data the page embeds,
`h(Status, { known })`, which is what
[`recipes/dom/`](/docs/recipes/dom) shows) or show nothing
while a hydration is open: `hydrating()` answers whether the mount running right now is claiming
server nodes, and `@aweftjs/ui`'s `suspend` uses it to keep its fallback off the page during one.

An empty text node is inserted rather than paired. `''` renders to no characters, so the
markup holds no node to pair it with, and a form's error line that is empty until something
goes wrong is the ordinary case. The node goes in where the cursor is and the cell behind it
writes into a node that is really on the page.

One live hydration per target: a second `hydrate` over the same target asserts, because it
would claim the nodes the first one holds. Remove the first, then hydrate again.

## The light tree

`createDocument()` makes a document with no browser behind it: `createElement`,
`createTextNode`, `createComment`, `body`, `head`, attributes, `style`, `classList`, listeners
that are kept and dispatched only by `dispatchEvent`. `toHtml(node)` serializes. It applies no
auto-closing or implied elements.

`parseHtml(markup, document)` reads markup back into light nodes and **answers the top-level nodes
it read, not a document**. Put them somewhere yourself, which is what makes a page renderable,
parseable and hydratable in a test with no browser:

```ts
const document = createDocument();
for (const node of parseHtml(markup, document)) document.body.appendChild(node);
hydrate(document.body, h(App, {}));
```

It is also where `h` makes a node when nobody has said where: inside a mount the mount says,
outside one the page's `document` does, and where there is no page the light tree registers
itself as the answer. Nothing imports the light tree to arrange that: it registers the
no-page fallback from its own file, and `html` is built with a pure annotation, so a bundler
that follows both can leave the tree and the template parser out of a page that writes neither.

A bundle without the light tree has no fallback, so a program with no page document mounts,
renders, or makes a document with `createDocument()` before it calls `h` at module scope. `h`
with no mount, no page and no registration asserts `no document to make nodes in: mount or
render into one` rather than guessing.

## Mount targets and mounters

The target of `mount` is any element, or anything with `insertBefore`, `removeChild` and
`replaceChild`, so a component can intercept what its children mount by handing them an
object of its own. `mount(target, item, before, context)` places the item before another
mount's first node (`before` is that mount's remove function) and hands `context` down
unchanged to every mounter below. A component may return a mounter, `(elem, item, before,
context) => remove`, and call `mount` itself with a context of its own; `before(getFirst)`
answers the node to insert before. The binding never reads the context.

`createElement`, `createTextNode`, `setAttribute` and `watch` are the pieces a custom `h`
needs: they create through whichever document the current mount renders into and apply the
binding's rules for attributes. `watch(value, fn)` is `effect` for a value that may or may
not be reactive: a plain value is delivered once, a cell's value now and after every commit.

The light tree's classes are exported as types (`LightDocument`, `LightElement`,
`LightNode`, `LightText`, `LightComment`), with the host shapes `NodeLike`, `ElementLike`,
`TextLike`, `CommentLike`, `ParentLike` and `DocumentLike`, and `H` for the `h` that `htm`
binds.

## The router

```ts
import { createRouter } from '@aweftjs/dom/router';

const router = createRouter({ base: '/docs' });
const stop = router.links(document.body);

router.url.effect((url) => console.log('now at', url));
router.push('/guide/install');
```

A subpath export, so a page that never routes never loads it. `createRouter({ url, base })`
gives you:

| name | what it is |
|---|---|
| `url` | a read-only cell: the path, query and hash showing now, relative to `base` |
| `key` | a read-only cell naming the history entry. The same key comes back with back and forward |
| `push(url)`, `replace(url)`, `back()` | the three ways to move. Both take a path relative to `base`; a whole URL, or a path that already carries `base`, is a loud assert |
| `links(root)` | takes over same-origin anchor clicks under `root`, and returns its unsubscribe |
| `saved()`, `restore()` | the scroll position kept for the entry showing now, and putting the page back on it |
| `stop()` | stop listening for entry changes |

One history path: `popstate`, `pushState` and `replaceState`. `history.scrollRestoration` is
`manual` and positions are kept per entry key in `sessionStorage`, saved before a navigation
leaves an entry. The router restores nothing on its own: it answers `saved()` and does what
`restore()` says, and when to use them is the component layer's decision.

`links` leaves a click alone when a modifier key is held, when it is not the primary button, when
something already prevented it, when the anchor has `target` (other than `_self`), `download` or
**`data-no-route`**, when the href is cross-origin, another scheme, or outside `base`, and when the
href is this same page with a different hash on it. That last one is the browser's work: it scrolls
to the target and writes the entry, and the router picks the new URL up like any other entry change.

**One router per page.** A second one over the same history mints keys from the same counter and
writes into the same scroll slots, so the two would trade entry keys and restore each other's
positions. Make one and hand it to whatever needs it.

**With no `window` it is the same router over a stack in memory.** `push`, `replace` and `back` all
still move `url`, which is what lets a static render open the page a URL names and a headless test
drive a whole navigation. `links` returns a working unsubscribe that removed nothing, `saved()`
answers null and `restore()` answers false.

## What it never decides

What a component renders, how a site walks its pages, head tags, themes, and what any of the
router's URLs mean. Storage and transport are not here: a document array on the page is the same
document array a store persists or a link shares.

## Boundaries

`@aweftjs/dom` imports `@aweftjs/core` and nothing else. The complete program using all of the
above, with its DOM operations asserted one by one against the recording host in
`@aweftjs/testing`, is
[`recipes/dom/`](/docs/recipes/dom).

## Known limits

**A hydration builds a tree it then throws away.** `h` makes an element the moment it runs, and
the pairing walk copies each fresh element's attributes and properties onto the server's node
and drops the fresh one, so taking over a page costs one `createElement` per element on it and
every node those calls returned is garbage by the end of the mount. On a page of a few dozen
elements that is a fraction of a frame; a page of hundreds has not been measured. Making it
zero means claiming the server's node before building a fresh one, which changes the mounting
model and the node factory every path here shares.

## The design notes

A `design NNN` above is the note of that number in
[`docs/design/`](https://github.com/torrinworx/aweft/tree/9a5bb24770dc7555257f8a307d79917efc58df70/docs/design), which says what was
decided, why, what it costs, and what would reverse it.

## API

Every export of `@aweftjs/dom`, its signature as the compiler resolves it, and its block comment.

### `@aweftjs/dom`

#### `Cleanup`

```ts
type Cleanup = (...fns: (() => void)[]) => void;
```

No block comment on this export.

#### `CommentLike`

```ts
interface CommentLike extends NodeLike { data: string; }
```

No block comment on this export.

#### `Component`

```ts
type Component<P = Record<string, unknown>> = (props: P & { children: unknown[]; each?: unknown; }, cleanup: Cleanup, mounted: Mounted, pending: Pending) => unknown;
```

A component: called once with its props, and what it returns is mounted.

#### `DocumentLike`

```ts
interface DocumentLike { createElement(tag: string): ElementLike; createElementNS(namespace: string, tag: string): ElementLike; createTextNode(data: string): TextLike; createComment(data: string): CommentLike; }
```

Where nodes come from. The page's `document`, or the light tree.

#### `ElementLike`

```ts
interface ElementLike extends NodeLike, ParentLike { readonly localName: string; readonly namespaceURI: string | null; setAttribute(name: string, value: string): void; getAttribute(name: string): string | null; hasAttribute(name: string): boolean; removeAttribute(name: string): void; getAttributeNames(): string[]; readonly firstChild: NodeLike | null; textContent: string | null; [property: string]: unknown; }
```

No block comment on this export.

#### `H`

```ts
type H = (tag: unknown, props: Record<string, unknown> | null, ...children: unknown[]) => unknown;
```

No block comment on this export.

#### `Hydrated`

```ts
type Hydrated = Remove & { readonly ready: Promise<void>; };
```

What `hydrate` answers: the remove function, and when the pairing walk finished.

#### `LightComment`

```ts
class LightComment
```

No block comment on this export.

#### `LightDocument`

```ts
class LightDocument
```

No block comment on this export.

#### `LightElement`

```ts
class LightElement
```

An element in the light tree, named the way a browser names one: `tagName` and `nodeName` are
the uppercase name for an HTML element and `localName` is the lowercase one, while an element
in another namespace keeps the case it was made with in all three. Match on `localName`.

#### `LightNode`

```ts
class LightNode
```

No block comment on this export.

#### `LightText`

```ts
class LightText
```

No block comment on this export.

#### `Mounted`

```ts
type Mounted = (...fns: (() => void)[]) => void;
```

No block comment on this export.

#### `Mounter`

```ts
type Mounter = (elem: ParentLike, item: unknown, before: Remove, context: unknown) => Remove;
```

A component that mounts itself: what `h` makes of a component, and what one may return.

#### `NodeLike`

```ts
interface NodeLike { readonly nodeType: number; readonly nodeName: string; parentNode: ParentLike | null; readonly nextSibling: NodeLike | null; readonly previousSibling: NodeLike | null; readonly firstChild: NodeLike | null; readonly lastChild: NodeLike | null; readonly ownerDocument?: DocumentLike | null; textContent: string | null; }
```

No block comment on this export.

#### `ParentLike`

```ts
interface ParentLike { insertBefore(node: NodeLike, before: NodeLike | null): unknown; removeChild(node: NodeLike): unknown; replaceChild(node: NodeLike, old: NodeLike): unknown; readonly firstChild?: NodeLike | null; textContent?: string | null; readonly ownerDocument?: DocumentLike | null; }
```

What a mount target must offer: the three operations the mounter uses on a parent.

#### `Pending`

```ts
type Pending = (promise: Promise<unknown>) => void;
```

No block comment on this export.

#### `Remove`

```ts
type Remove = (arg?: typeof getFirst) => NodeLike | null | undefined;
```

What `mount` returns: call it to unmount, or with `getFirst` for the first live node.

#### `Template`

```ts
type Template = (values: readonly unknown[]) => unknown;
```

One instance of a template: what `h` would have returned for the same subtree.

#### `TemplateAttributes`

```ts
type TemplateAttributes = Readonly<Record<string, string | number | boolean>>;
```

The attributes of one element in a template: only values that were literal in the source.

#### `TemplateChild`

```ts
type TemplateChild = string | TemplateElement;
```

What sits inside an element in a template: text, or another element.

#### `TemplateEdit`

```ts
type TemplateEdit = readonly [ 'props', path: readonly number[] ] | readonly [ 'child', path: readonly number[], before: number ];
```

Where something varies. `path` is the child indices from the root to the element it belongs
to; `[]` is the root itself. A `props` edit takes an object of properties and attributes; a
`child` edit takes one item, and `before` is the index of the static child it goes in front
of, or -1 for the end.

#### `TemplateElement`

```ts
type TemplateElement = readonly [ tag: string, attributes: TemplateAttributes | null, ...children: TemplateChild[] ];
```

One element in a template: its name, its literal attributes, and its static children.

#### `TextLike`

```ts
interface TextLike extends NodeLike { data: string; splitText(offset: number): TextLike; }
```

No block comment on this export.

#### `createDocument`

```ts
createDocument: () => LightDocument
```

A document with no browser behind it.

**Returns** a light document with `documentElement`, `head` and `body`, and the factories a mount needs. Nodes made by different light documents mix freely.

**Example**

```ts
const doc = createDocument();
mount(doc.body, h('p', {}, 'hello'));
toHtml(doc.body);  // '<body><p>hello</p></body>'
```

#### `createElement`

```ts
createElement: (tag: string, namespace?: string | undefined) => ElementLike
```

Make an element through the document the current mount renders into.

**Params**

- `tag`: the element name
- `namespace`: an XML namespace, for `svg` and the like; omitted for HTML

**Returns** a fresh, unattached element. Outside any mount it comes from the page's document, or from a light document where there is no page.

**Throws** an assert, loud in development and stripped in a release build, when there is no page and no mount is running, so there is no document to make the node in.

**Example**

```ts
const circle = createElement('circle', 'http://www.w3.org/2000/svg');
```

#### `createTextNode`

```ts
createTextNode: (text: string) => TextLike
```

Make a text node through the document the current mount renders into.

**Params**

- `text`: its content

**Returns** a fresh, unattached text node.

**Throws** an assert, loud in development and stripped in a release build, when there is no page and no mount is running, so there is no document to make the node in.

**Example**

```ts
const label = createTextNode('hello');
```

#### `getFirst`

```ts
getFirst: typeof getFirst
```

Ask a remove function for the first live node of its mount instead of removing it.

`mount` returns a function; called with nothing it unmounts, called with `getFirst` it
answers the first node the mount put in the document, or the node after it when the mount
has none. A component that returns a mounter uses it on the `before` it was handed.

**Example**

```ts
const anchor = before(getFirst);
```

#### `h`

```ts
h: (tag: unknown, props?: Record<string, unknown> | null, ...children: unknown[]) => unknown
```

Make an element, or a component's mounter.

**Params**

- `tag`: an element name, an existing node to use as the element, or a component function
- `props`: attributes by bare name, properties by `$name` (`$value`, `$onclick`, a `$style` object), each static or a scope, cell or derived value; `children` as an alternative to the rest arguments; `each` on a component to mount it once per item of a list
- `children`: what goes inside: primitives, nodes, `h()` output, iterables, scopes and cells, components. `null` is skipped and `undefined` is refused

**Returns** the element itself when nothing in it is reactive, so it can be used as a node; otherwise a value `mount` binds. For a component, a mounter to hand to `mount`.

**Throws** an assert, loud in development and stripped in a release build, for a null tag, a tag that is neither an element name nor a component, `undefined` as a child, children given both ways, or an object written as an attribute.

**Example**

```ts
mount(document.body, h('button', { $onclick: () => count.set(count.get() + 1) }, 'clicked ', count));
```

#### `htm`

```ts
htm: (h: H, options?: { join?: ((parts: unknown[]) => unknown) | undefined; }) => (strings: TemplateStringsArray, ...values: unknown[]) => unknown
```

Bind the template parser to an `h`.

**Params**

- `h`: the element factory the markup calls
- `options`: `join`, how a quoted attribute of several parts becomes one value; by default plain parts concatenate and a part that is a scope or cell makes the whole value derived

**Returns** a tag function for template literals.

**Throws** the tag function asserts, loud in development and stripped in a release build, on markup it cannot read: an unclosed tag, an attribute with no value, a comment or a quote left open, or a closing tag that matches nothing.

**Example**

```ts
const html = htm(myH);
html`<p class="note ${tone}">${text}</p>`
```

#### `html`

```ts
html: (strings: TemplateStringsArray, ...values: unknown[]) => unknown
```

Markup in a template literal, bound to `h`.

Annotated pure so a bundler drops the binding, and the parser behind it, out of a page that
never writes `html`. Nothing else in the package reaches `htm`.

**Example**

```ts
mount(document.body, html`<button $onclick=${add}>clicked ${count} times</button>`);
```

#### `hydrate`

```ts
hydrate: (target: ParentLike, item: unknown, context?: unknown) => Hydrated
```

No block comment on this export.

#### `hydrating`

```ts
hydrating: () => boolean
```

Whether the mount running right now is claiming server nodes. A hoisted template asks,
because a claimed node has to be one the binding made rather than a clone.

#### `isComponentCall`

```ts
isComponentCall: (value: unknown) => boolean
```

Is this already the mounter `h` makes of a component, rather than a bare function?

**Params**

- `value`: anything an application would hand `mount`, `render`, `hydrate` or `attach`

**Returns** true for what `h(Component, props)` answers, false for everything else, a bare function included. Whoever wants a bare function mounted as a component with no props asks first, because wrapping a component call a second time would call it with a component's arguments. `hydrate` asks (design 157), and so does `ssg`'s `attach`.

**Example**

```ts
const mounted = typeof item === 'function' && !isComponentCall(item) ? h(item) : item;
```

#### `joined`

```ts
joined: (parts: unknown[]) => unknown
```

One attribute value out of several parts.

A quoted attribute in markup may mix text and expressions, and this is what the pieces become.
`build` emits a call to it for a compiled template, so markup that was parsed and markup that
was compiled make the same value out of the same pieces.

**Params**

- `parts`: the pieces in order, each a plain value or a scope, cell or derived value

**Returns** the pieces joined as text when all of them are plain, and otherwise a derived value that joins them again whenever one of them changes.

**Example**

```ts
setAttribute(el, 'class', joined(['note ', tone]));
```

#### `mount`

```ts
mount: (elem: ParentLike, item: unknown, before?: Remove | undefined, context?: unknown) => Remove
```

Mount an item under a target.

**Params**

- `elem`: an element, or anything with `insertBefore`, `removeChild` and `replaceChild`
- `item`: null, a primitive, a node, `h()` output, an iterable, a document array, a mutable array, a scope, cell or derived value, or a mounter
- `before`: the anchor: a remove function whose `getFirst` answer the item goes before. Omitted, the item goes at the end
- `context`: an opaque value handed to every mounter below, and to nothing else

**Returns** the remove function. Call it to unmount; call it with `getFirst` for the first live node. `undefined` is refused: hide something with `null`.

**Throws** an assert, loud in development and stripped in a release build. `undefined` as the item, `null` as the anchor, a plain object, a node already mounted elsewhere and a target with no document are all caller mistakes rather than data the mounter refuses.

**Example**

```ts
const stop = mount(document.body, h('p', {}, 'hello ', name));
stop();
```

#### `parseHtml`

```ts
parseHtml: (markup: string, document?: LightDocument) => LightNode[]
```

Parse markup into light nodes.

**Params**

- `markup`: what `toHtml` wrote, or any markup in that shape
- `document`: the light document the nodes belong to; a fresh one when omitted

**Returns** the top-level nodes in order. Tags, attributes, comments and entities are read; the browser's implied elements and auto-closing rules are not applied, so give it markup that needs neither.

**Example**

```ts
const [page] = parseHtml(html);
hydrate(doc.body, App);
```

#### `render`

```ts
render: (item: unknown, options?: { context?: unknown; }) => Promise<string>
```

Render an item to markup, with no browser.

**Params**

- `item`: anything `mount` takes
- `options`: `context`, the opaque value every mounter below receives

**Returns** the markup, once nothing a component declared `pending` is still pending. Every dynamic part (a scope, a component, each item of a list) is bracketed with `<!--[-->` and `<!--]-->` so `hydrate` can find it. No doctype; the page adds its own.

**Throws** whatever mounting the item throws, which for a caller mistake is an assert, loud in development and stripped in a release build.

**Example**

```ts
const page = await render(h(App, { url }));
```

#### `setAttribute`

```ts
setAttribute: (element: ElementLike, name: string, value: unknown) => void
```

Write an attribute the way the binding does.

**Params**

- `element`: the element
- `name`: the attribute
- `value`: `null`, `undefined` and `false` remove it; `true` sets it empty; anything else is written as text

**Example**

```ts
setAttribute(input, 'disabled', busy.get());
```

#### `template`

```ts
template: (spec: TemplateElement, edits: readonly TemplateEdit[]) => Template
```

The static shape of a subtree, made once per document and instanced per use.

**Params**

- [`spec`](/spec): the element, its literal attributes and its static children, nested
- `edits`: where something varies, in the order a source evaluates the values: for each element its properties first, then its children, and a nested element's whole subtree where that child sits

**Returns** a function taking one value per edit, in the same order, and returning what `h` would have returned. Hand that straight to `mount`. The values are applied in a different order from the one they are given in: every element's children before its own properties, so a property that rewrites the element's content still wins.

**Throws** the instancing function asserts, loud in development and stripped in a release build, for an edit path the element tree does not have, one element's child edits listed in two places, or `undefined` where a value goes.

**Example**

```ts
const row = template(['li', { class: 'row' }, ['span', null]], [['child', [0], -1]]);
mount(list, row([title]));
```

#### `toHtml`

```ts
toHtml: (node: NodeLike | readonly NodeLike[]) => string
```

Markup for a light node, or for a list of them.

**Params**

- `node`: a light node, or an array of light nodes serialized in order

**Returns** the markup, with text and attribute values escaped, void elements unclosed and `script` and `style` contents raw. No doctype; a page adds its own.

**Example**

```ts
toHtml(doc.body.childNodes);
```

#### `watch`

```ts
watch: (value: unknown, callback: (value: unknown) => void) => () => void
```

Run a callback for a value now and, when the value is a scope, cell or derived value, again
after every change.

**Params**

- `value`: a plain value, or anything with `get` and `effect`
- `callback`: what to run with each value

**Returns** the unsubscribe. For a plain value it does nothing.

**Example**

```ts
const stop = watch(props.title, (title) => { element.title = String(title); });
```

### `@aweftjs/dom/router`

#### `Entries`

```ts
interface Entries { current(): string; state(): unknown; push(state: unknown, href: string): void; replace(state: unknown, href: string): void; back(): void; listen(fn: () => void): () => void; }
```

Where the URL and its history entries live (design 282). The browser's history is the usual
one; a router inside a sandbox frame runs over an object that reads and writes a document
shared across the wall. The router stamps its own entry key into the state it pushes and
reads it back through `state`, so an implementation keeps whatever state it is handed, per
entry, and answers it for the entry showing now.

#### `LinkRoot`

```ts
interface LinkRoot { addEventListener(type: string, listener: (event: never) => void): void; removeEventListener(type: string, listener: (event: never) => void): void; }
```

What `links` attaches to: the element clicks are watched on.

#### `Router`

```ts
interface Router { readonly url: Derived<string>; readonly key: Derived<string>; readonly base: string; push(url: string): void; replace(url: string): void; back(): void; links(root: LinkRoot): () => void; saved(): ScrollPosition | null; restore(): boolean; stop(): void; }
```

The URL, the history entry, and the ways to move.

#### `RouterOptions`

```ts
interface RouterOptions { readonly url?: string; readonly base?: string; readonly entries?: Entries; }
```

What `createRouter` takes.

#### `ScrollPosition`

```ts
interface ScrollPosition { readonly x: number; readonly y: number; }
```

Where a page was scrolled to, in CSS pixels from the top left.

#### `createRouter`

```ts
createRouter: (options?: RouterOptions) => Router
```

Make a router over the page's history, or over a stack in memory where there is no page.

**Params**

- `options`: `url`, where to start with no `window` (`/` when left off, ignored in a browser), `base`, a path every URL is under (`''` when left off, no trailing slash), and `entries`, where the URL and its history entries live when they are not the window's (design 282); scroll, storage and clicks still come from the window when there is one, and an anchor click is then resolved against the entries' own URL

**Returns** the router. `url` and `key` are read-only cells; write to them and they throw, because `push` and `replace` are how a router moves.

**Throws** an assert, loud in development and stripped in a release build, when `base` does not start with `/`, which is the mistake that silently routes nothing.

**Example**

```ts
const router = createRouter({ base: '/docs' });
const stop = router.links(document.body);
router.push('/guide/install');
router.url.get();  // '/guide/install'
```

## Refusals

Every error this package raises carries a `reason` to switch on and a `fix` that says what to do. These are its reasons, from `errors.txt`.

| reason | fix |
|---|---|
| `not-a-child` | Call removeChild on the node's own parent, which parentNode names. |
| `not-a-child` | Pass one of this node's children as the reference, or null to append. |

## Recipes

The programs in the stack's gate that use this package, each a job someone would have.

- [`recipes/todo-list`](/docs/recipes/todo-list): A list you add to, toggle, filter and reorder, with the list following each edit rather than being rebuilt
- [`recipes/ui`](/docs/recipes/ui): A page with themes, contexts, control flow, a popup and a suspend, built by vite and driven in a real browser
- [`recipes/routed-site`](/docs/recipes/routed-site): A site with real URLs: nested pages, a page with a parameter, a page that arrives later, a dialog the back button dismisses, and a title per page
- [`recipes/ssg`](/docs/recipes/ssg): A routed site written out as files, served by anything, and taken over in place when the browser gets to it
- [`recipes/dom`](/docs/recipes/dom): the package's own recipe
