# VisualMap

Group: Canvas

A working, interactive Visualli map for a `.visualli` document: layers, rings, connectors, peeks, term cards, Go deeper, stepping inside and the gradual-reveal bloom — on mouse and on touch.
- Provide `doc` `{title, layers, anchors?}` — layers exactly as in the open spec (`nodes`, `connections`, `containers`, `level`, `parentLayerId`, `parentNodeId`); `anchors` are the `semantic-anchors` extension's terms, underlined in summaries. Optional `height`, `insets` (space covered by floating chrome, so the map fits around it), `source` (shown on fact cards), `immersion`, `showTrail`, `showControls`, `onLayerChange`.
- **Interaction follows the pointer in use** (`interaction="auto"`, the default; pin `"hover"` or `"touch"` for previews and tests):
  | | Mouse / trackpad | Touch / pen |
  | --- | --- | --- |
  | Peek an idea | Hover or focus → `SemanticPeek` beside it | **First tap** → idea is selected, `SemanticPeek` opens as a bottom sheet |
  | Step inside | Click a ringed idea, Enter, or *Step inside* | **Second tap** on the selected idea, or *Step inside* |
  | Go deeper | *Go deeper* on a peek (the idea) or a term card (the term) → `GoDeeper` panel | Same → `GoDeeper` sheet |
  | Close the preview | Move away, Escape, click empty canvas | Tap empty canvas, swipe the sheet down, ✕, Escape |
| **Move an idea** (`editable`) | Press and drag (after 4px) · Option/Alt + arrows on a focused idea (Shift = bigger steps) | Press and move (after 10px) — a still press stays a tap |
  | Defined term | Click the underlined word → definition in the same card, *‹ back* to the idea | Same, inside the sheet |
- On touch the selected idea lifts, gets a heavier outline and the others dim to 60%; the canvas nudges it into the space above the sheet. `onSelect(node | null)` tells the host when a sheet is up — hide competing bottom chrome (the Composer) meanwhile.
- `touchHint` shows a one-time Caveat note on touch ("tap an idea to peek · tap again to step inside") until the first tap; it's remembered per browser. Pass a string to change it, `false` to hide it.
- Escape/Backspace or the trail backs out a layer. Honors the five comfort attributes; the sheet slides in, or fades under Reduce motion.
- **Ideas are movable** (`editable`, default `true`) so people can arrange a map their way. Connectors re-route as an idea moves (they always run between the current positions, outside the rings). A moved idea lifts and casts a deeper shadow while held; it can't leave the visible canvas. Persist each move from `onNodeMove({ layerId, nodeId, position })` into the node's `position` in the `.visualli` file and pass the updated `doc` back; *Reset layout* (↶ in the canvas controls) restores the file's original positions for the current layer and reports them through `onNodeMove` too. Set `editable={false}` where a map is shown rather than owned — embeds in other apps (MCP), or marketing pages on phones, where a finger should scroll the page.
- **Go deeper:** `onGoDeeper(target)` fires when someone asks (`target` = `{ kind: 'idea', node }` or `{ kind: 'term', node, anchor }`, plus `layerId`) — start generating then. `facetsFor(target)` returns the five facets with their current status and content; re-render as they stream. `onVisualize(target, 'inside' | 'new')` is *Map it*. `deeperLayout` (`auto` | `panel` | `sheet` | `stack`) and `onDeeperChange(target | null, layout)` let the page move its own chrome aside for the panel.
- **Side panels and spotlight:** `aside={(layout, style) => <SourcesPanel layout={layout} style={style} … />}` docks a host panel in Go deeper's slot with the same layout choice and insets (opening it closes Go deeper). `highlight={ids}` spotlights ideas and dims the rest — e.g. while a source passage is hovered.
- **Editing** (`editing` + `onEditingChange`, `onEdit`): browsing keeps working while editing. Click/tap selects an idea, connector or group and shows its `SelectionBar`; click a selected idea (or double-click) to rename it in place; double-click empty canvas to add an idea; Delete/Backspace removes the selection (ideas with inner layers ask first); F2 renames; ⌘Z / ⇧⌘Z undo and redo; Escape steps back. The `EditBanner` sits top-center inside the map's top inset — reserve room for it in your chrome. Every change reaches `onEdit(op, doc)`; persist `doc`. `autoEditOnMove` (default false) switches to edit mode on the first drag instead of showing the invitation.
- This is the reference behavior for the production Canvas 2D renderer, which shares `blobPath`/`drawBlob`, `RINGS`, `edgePath` and `usePointerMode`.


## Props

```ts
export interface VisualMapProps { doc: MapDoc; height?: number | string; insets?: { top?: number; right?: number; bottom?: number; left?: number }; immersion?: boolean; showTrail?: boolean; showControls?: boolean; source?: string; onLayerChange?: (layer: MapLayer) => void; className?: string;
```

## Use

```ts
import { VisualMap } from '@visualli/design-system/components';
```
