# TileMapCallout

A MapLibre-aware callout for TileMap. Render it as a child of any
TileMap, pass a longitude/latitude pair, and the component manages marker
placement and lifecycle through the map context.

**Category:** Components/Graphics/TileMapCallout

**Import:** `import { TileMapCallout } from '@reuters-graphics/graphics-components'`

## Props

| Prop | Type | Default | Required | Description |
|------|------|---------|:--------:|-------------|
| `lngLat` | `TileMapCalloutCoordinates` | — | ✓ | Longitude/latitude to attach the callout to. Accepts [lng, lat] or an object with lng/lat, lon/lat or longitude/latitude. |
| `children` | `Snippet` | — |  | Generic callout content. Keep it short so it fits on small map viewports. |
| `placement` | `TileMapCalloutPlacement` | `'above'` |  | Place the callout surface above or below the map coordinate. Options: `above`, `below` |
| `flip` | `boolean` | `false` |  | Flip the callout surface to the left of the map coordinate. |
| `class` | `string` | `''` |  | Add custom classes to the callout surface. |
| `id` | `string` | `''` |  | Add an id to the MapLibre marker wrapper. |
| `leaderWidth` | `number` | `DEFAULT_TILE_MAP_CALLOUT_LEADER_WIDTH` |  | Leader-line width in px — the horizontal distance from the marker dot to the callout surface. Also sets the surface's offset from the dot so the line and surface always line up. Clamped to at least the dot diameter (and at least `1px`) so a `0`-ish value can't collapse the SVG. Defaults to `14`. |
| `leaderHeight` | `number` | `DEFAULT_TILE_MAP_CALLOUT_LEADER_HEIGHT` |  | Leader-line height in px — the vertical distance the diagonal rises (`placement="above"`) or drops (`placement="below"`). Defaults to `14`. |
| `dotRadius` | `number` | `DEFAULT_TILE_MAP_CALLOUT_DOT_RADIUS` |  | Marker dot radius in px. Defaults to `3`. |
| `surface` | `TileMapCalloutSurface` | `'filled'` |  | Surface chrome. `"filled"` (default) draws the themed box (background, border and shadow). `"bare"` removes that chrome so you can render your own container inside the callout — the marker, leader line and positioning still apply. Useful when wrapping a pre-styled card. Options: `filled`, `bare` |

## Types

```typescript
export type TileMapCalloutPlacement = 'above' | 'below';

export type TileMapCalloutCoordinates =
    | [number, number]
    | { lng: number; lat: number }
    | { lon: number; lat: number }
    | { longitude: number; latitude: number };

export type TileMapCalloutSurface = 'filled' | 'bare';

export interface TileMapCalloutGeometry {
    /** SVG canvas width and the surface's inline offset from the dot (px). */
    leaderWidth: number;
    /** SVG canvas height (px). */
    svgHeight: number;
    /** Dot centre x for the default (non-flipped) side. */
    dotCx: number;
    /** Dot centre x when flipped to the right of the surface. */
    dotCxFlipped: number;
    /** Dot centre y for `placement="above"`. */
    dotCyAbove: number;
    /** Dot centre y for `placement="below"`. */
    dotCyBelow: number;
  }
```

## Examples

### Drop-in TileMap callout

```svelte
<TileMap
  id="tile-map-callout-demo"
  center={[-73.9868, 40.7567]}
  zoom={13}
  interactive={true}
  title="TileMap callout"
  description="Drop a callout directly inside TileMap. TileMapCallout handles the MapLibre marker lifecycle and stays attached as the map moves."
  height="500px"
>
  <TileMapCallout lngLat={[-73.9868, 40.7567]}>Times Square</TileMapCallout>
</TileMap>
```

### Placement and flip

```svelte
<TileMap
  id="tile-map-callout-placement-demo"
  center={[-73.9868, 40.7567]}
  zoom={13}
  interactive={true}
  title="Callout placement"
  description="Use placement and flip to keep labels clear of nearby map features or viewport edges."
  height="500px"
>
  <TileMapCallout lngLat={[-73.9868, 40.7567]} placement="above">
    Above anchor
  </TileMapCallout>
  <TileMapCallout lngLat={[-73.9776, 40.7527]} placement="above" flip>
    Above + flipped
  </TileMapCallout>
  <TileMapCallout lngLat={[-73.9942, 40.7505]} placement="below">
    Below anchor
  </TileMapCallout>
  <TileMapCallout lngLat={[-73.9819, 40.7681]} placement="below" flip>
    Below + flipped
  </TileMapCallout>
</TileMap>
```

### Custom leader geometry

```svelte
<TileMap
  id="tile-map-callout-leader-demo"
  center={[-73.9868, 40.7567]}
  zoom={13}
  interactive={true}
  title="Custom leader geometry"
  description="Tune the leader line and dot with the leaderWidth, leaderHeight and dotRadius props. The surface offset and the SVG stay in sync."
  height="500px"
>
  <TileMapCallout lngLat={[-73.9868, 40.7567]}>
    Default (14×14, dot 3)
  </TileMapCallout>
  <TileMapCallout
    lngLat={[-73.9776, 40.7527]}
    leaderWidth={40}
    leaderHeight={32}
    dotRadius={5}
  >
    Long leader (40×32, dot 5)
  </TileMapCallout>
  <TileMapCallout
    lngLat={[-73.9942, 40.7505]}
    leaderWidth={6}
    leaderHeight={6}
  >
    Short leader (6×6)
  </TileMapCallout>
</TileMap>
```

### Rich content

```svelte
<TileMap
  id="tile-map-callout-rich-demo"
  center={[-73.9868, 40.7567]}
  zoom={12}
  interactive={true}
  title="Rich callout content"
  height="500px"
>
  <TileMapCallout lngLat={{ longitude: -73.9868, latitude: 40.7567 }}>
    <div class="rich-callout">
      <strong>Reuters</strong>
      <span>Times Square newsroom</span>
    </div>
  </TileMapCallout>
</TileMap>
```

### Bare surface

```svelte
<TileMap
  id="tile-map-callout-bare-demo"
  center={[-73.9868, 40.7567]}
  zoom={13}
  interactive={true}
  title="Bare surface"
  description="With surface='bare' the callout draws no box — render your own pre-styled container inside. The marker, leader line and positioning still apply."
  height="500px"
>
  <TileMapCallout lngLat={[-73.9868, 40.7567]} surface="bare">
    <div class="bare-card">
      <strong>Times Square</strong>
      <span>Bring your own box</span>
    </div>
  </TileMapCallout>
</TileMap>
```

## Documentation

# TileMapCallout

A labelled callout for [`TileMap`](/docs/components-graphics-tilemap--docs). Drop it in as a child of a `TileMap`, give it a coordinate, and it manages the MapLibre marker lifecycle for you — placing a leader line and label that stay pinned to the point as the map pans and zooms.

```svelte
<script>
  import {
    TileMap,
    TileMapCallout,
  } from '@reuters-graphics/graphics-components';
</script>

<TileMap id="my-map" center={[-73.9868, 40.7567]} zoom={13} height="500px">
  <TileMapCallout lngLat={[-73.9868, 40.7567]}>Times Square</TileMapCallout>
</TileMap>
```

## Placement and flip

Use `placement` (`above` or `below`) and `flip` to position the label around its anchor so it stays clear of nearby map features or the viewport edges. Pass any content as children — a short string or richer markup.

## Leader geometry

The leader line and marker dot default to a compact 14×14px line with a 3px dot. Tune them per callout with the `leaderWidth`, `leaderHeight` and `dotRadius` props (all in px). The callout surface's offset from the dot is derived from `leaderWidth`, so the line and the label stay aligned at any size.
When values collapse to `0`, the component clamps the SVG canvas to a positive size (at least the dot diameter, and at least `1px`) so the SVG/viewBox can’t collapse to `0`.

```svelte
<TileMapCallout
  lngLat={[-73.9868, 40.7567]}
  leaderWidth={40}
  leaderHeight={32}
  dotRadius={5}
>
  Long leader
</TileMapCallout>
```
