# `StatifierBlocks.Editor.MapRegions`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/editor/map_regions.ex#L2)

The two function components a host mounts to show the Map beside its own
list: `map_region/1`, where the `StatifierBlocksMap` hook draws the
document, and `description_region/1`, which says in words what the
selected block is, or what the document is when nothing is selected.

They live in the `StatifierBlocks.Editor.*` namespace because they name
Phoenix, and that namespace is where ADR-0005 decision 1's compile guard
lives. The editor draws no map and never mounts `map_region/1`
(ADR-0018). It does mount `description_region/1`, under its canvas,
from its own document, view model, palette and selection (ADR-0005's
Amendment of 2026-09-29 on the shell arrangement); see "In the editor"
below.

## What the host brings

The components keep no state. A host passes four things, and each stays
the host's:

| The host's | What it is |
|---|---|
| view model | `StatifierBlocks.ViewModel.build/3` of the document, which both regions read; the description region also takes the document and the palette it was built from |
| selection | the block id its list has selected, or `nil`; the map marks that block's box and the description region describes it |
| event names | the names its list already sends to select a block and to open an insert; a gesture on the map is sent under those names, with the payload the list sends for the same gesture |
| list | the list itself, which stays the keyboard and screen-reader path to every step |

## The map region

`map_region/1` renders the element the hook is attached to, inside a
region hidden from assistive technology (`aria-hidden="true"`): a reader
of the host's list would otherwise meet every step twice. The element
scrolls, and a browser makes a scroll box with nothing focusable in it a
Tab stop of its own, so it carries `tabindex="-1"`; nothing else in the
region takes focus.

The element carries what the hook reads, in the attributes it reads them
from: the graph `StatifierBlocks.Map.graph/2` answers as JSON in
`data-graph`, with the selection marked in it, and the selected block's
id again in `data-selected`, which the hook falls back on when the graph
carries no mark; whether the page can edit in `data-editable`; the
list's event names in `data-select-event` and `data-insert-event`; the
element to scroll into view after an insert armed from the map in
`data-insert-reveal`, when the host names one; and the ids of the
description region's hover layer and of its store in `data-info-hover`
and `data-info-store`, when the host names the region, which is what
gives the map its hover. The hook never reads the region itself, so its
id is not stamped. The drawing goes into a child marked
`data-map-canvas`, which LiveView leaves alone (`phx-update="ignore"`).

A document with drafts (steps not wired into the flow) gets them in a
drawer under the drawing, laid out apart from the path. If that layout
alone fails, the hook leaves the drawer out and draws the path; only a
path that cannot be laid out draws the error pane. A read-only page draws
the drawer closed, and the hook keeps no state of its own (ADR-0005
decision 7f), so an opened drawer closes again whenever the graph
changes and the drawing is replaced; the list still holds every draft.

## The description region

`description_region/1` renders the region with `aria-live="polite"`, so
a selection made in the list is read out, under the id the host's list
rows name with `aria-describedby`. Its content is rendered on the
server: the selected block's `StatifierBlocks.Map.Info` description, or
`StatifierBlocks.Map.Info.idle/4` when nothing is selected. A block's
author-written note leads, above the built-in text (ADR-0001's Amendment
of 2026-09-28, clause `2g`). It shows values and never controls. The
region's accessible name is its `aria-label`, `"Description"` unless the
host passes its own words in `label`.

Beside the region, and hidden, it renders the store the hook's hover
reads: one child per element the map draws, `data-describes` naming the
element's map id, holding the same markup the region shows for it. The
store's id is the region's with `-store` after it, which is the id
`map_region/1` stamps when it is handed the region's.

## Selection speaks, hover is silent

What the region announces changes on a selection and never on a hover.
The server writes the region, and only on a render: a row selected in
the list, or a block selected on the map, which arrives as the list's
own event. A hover is drawn in the hover layer instead, an element
beside the region and outside it, with the region's id and `-hover`
after it: `aria-hidden="true"`, `hidden` until the hook fills it, and
left alone by LiveView (`phx-update="ignore"`). The two sit in one
frame, `sb-map__description-frame`, and while the layer is shown the
stylesheet stacks it over the region and makes the region transparent,
so the visible text is the hovered element's while the region's own
content, and its `aria-live`, stay as the server wrote them. A class
the host passes in `class` goes on the layer as well as on the region,
so styling the host gives the region through it still shows while the
layer is drawn in the region's place.

## In the editor

`StatifierBlocks.Editor` renders `description_region/1` under its
canvas with `map={false}`, which says no map is mounted beside the
region. The region then renders without its hover layer and without
its store, which only the Map's hook reads, and the document's idle
description leaves out its explanation, the paragraph on how to read
the map, and its "Legend" fact, the lines of the map's key; the name,
the description, what starts it and the counts stay. A selected
block's description is unchanged. The default, `map={true}`, renders
the region exactly as a host's page has it.

## A document it cannot describe

Every description is read off `StatifierBlocks.Describe.outline/3`,
which expands each composite in the document, so a composite whose
declaration cannot expand makes the outline raise. The region degrades
rather than raising out of the host's render, as the editor does, which
leaves its region out for such a document: it renders no description at
all, neither a block's nor the document's idle one, and carries no
`data-map-description`. The region itself, its id, `aria-live` and
label stay, so the rows that name it still name an element; with
`map={true}` the hover layer stays and the store is rendered with no
entries, so a hover on the map shows nothing. The
rescue is exactly as wide as the editor's own check before it mounts
the region: any raise out of the outline.

# `description_region`

```elixir
@spec description_region(map()) :: Phoenix.LiveView.Rendered.t()
```

The Map's description region, its hover layer and its hidden store; see the moduledoc.
## Attributes

* `id` (`:string`) (required) - the region's id, which the host's list rows name with `aria-describedby`.
* `document` (`StatifierBlocks.Document`) (required) - the document the view model was built from.
* `view_model` (`StatifierBlocks.ViewModel`) (required)
* `palette` (`StatifierBlocks.Palette`) (required) - the palette the view model was built with.
* `selected` (`:string`) - the block id the host's list has selected. Defaults to `nil`.
* `phrase` (`:any`) - the host's words for event names, the function `map_region/1` takes: `nil` or a function of one argument; any other value raises `ArgumentError`. Defaults to `nil`.
* `map` (`:boolean`) - whether a map is mounted beside the region; `false` renders no hover layer, no store, and no how-to-read paragraph or key in the idle description. Defaults to `true`.
* `label` (`:string`) - the live region's accessible name, rendered as its `aria-label`; a host passes its own words for it, in its own language. Defaults to `"Description"`.
* `class` (`:string`) - a class of the host's, added to the region's own and to its hover layer's. Defaults to `nil`.

# `map_region`

```elixir
@spec map_region(map()) :: Phoenix.LiveView.Rendered.t()
```

The region the `StatifierBlocksMap` hook draws the Map into; see the moduledoc.
## Attributes

* `id` (`:string`) (required) - the id of the element the hook is attached to.
* `view_model` (`StatifierBlocks.ViewModel`) (required)
* `selected` (`:string`) - the block id the host's list has selected. Defaults to `nil`.
* `select_event` (`:string`) - the event the host's list sends to select a block. Defaults to `nil`.
* `insert_event` (`:string`) - the event the host's list sends to open an insert. Defaults to `nil`.
* `editable` (`:boolean`) - whether the map's gaps and markers arm inserts. Defaults to `false`.
* `phrase` (`:any`) - the host's words for event names, as `StatifierBlocks.Map.graph/2` takes them: `nil` or a function of one argument; any other value raises `ArgumentError`. Defaults to `nil`.
* `description` (`:string`) - the id of the host's `description_region/1`, which gives the map its hover. Defaults to `nil`.
* `insert_reveal` (`:string`) - a selector for what to scroll into view after an insert armed from the map. Defaults to `nil`.
* `class` (`:string`) - a class of the host's, added to the region's own. Defaults to `nil`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
