# react-mapbox

React components and hooks for Mapbox GL maps with Three.js custom layers.

```typescript
import {
    MapboxMap,
    MapboxCustomLayer,
    useMap,
    useCustomLayer,
} from '@yaoc1996/js-tools/react-mapbox'
```

---

## Components

---

### `MapboxMap`

Root component that initialises a Mapbox `Map` instance and provides it to all descendants via `MapboxMapContext`. Renders a full-size `<div>` container; the map fills the parent element.

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | — | Components nested inside the map (e.g. `MapboxCustomLayer`). |
| `options` | `Omit<MapOptions, 'container'>` | — | Mapbox `MapOptions` minus `container` (supplied internally). |

#### Example

```typescript
<div style={{ width: 800, height: 600 }}>
    <MapboxMap options={{ style: 'mapbox://styles/mapbox/streets-v12', center: [0, 0], zoom: 2 }}>
        {/* nested layers here */}
    </MapboxMap>
</div>
```

#### Notes

- Waits for the `style.load` event before exposing the `Map` instance to context consumers.
- On unmount the map is removed after a 300 ms delay to allow outgoing transitions to complete.
- `options` is deep-compared; the map is only recreated when a value actually changes.

---

### `MapboxCustomLayer`

Adds a Three.js `CustomLayer` to the nearest `MapboxMap` and provides it to descendants via `MapboxCustomLayerContext`. Automatically sets up ambient, point, and sun lights; cleans up on unmount.

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `anchor` | `LngLatLike` | — | Geographic origin for the Three.js world coordinate system. |
| `renderingMode` | `'2d' \| '3d'` | `'3d'` | Mapbox custom-layer rendering mode. |
| `children` | `ReactNode` | — | Hook consumers and Three.js object components. |

#### Example

```typescript
<MapboxMap options={{ style: 'mapbox://styles/mapbox/streets-v12', center: [114, 22], zoom: 14 }}>
    <MapboxCustomLayer anchor={[114, 22]}>
        {/* use useCustomLayer() here */}
    </MapboxCustomLayer>
</MapboxMap>
```

#### Notes

- Must be rendered inside a `MapboxMap` component; does nothing until `useMap()` returns a non-null map.
- The layer is removed with `map.removeLayer` on cleanup.

---

## Hooks

---

### `useMap()`

Returns the current Mapbox `Map` instance from the nearest `MapboxMap` context, or `null` if the map has not yet loaded.

#### Returns

`Map | null | undefined` — `null` while the map is initialising; the `Map` instance once loaded; `undefined` if called outside a `MapboxMap` provider.

#### Example

```typescript
const map = useMap()

useEffect(() => {
    if (map) {
        map.flyTo({ center: [114, 22], zoom: 16 })
    }
}, [map])
```

---

### `useCustomLayer()`

Returns the current `CustomLayer` from the nearest `MapboxCustomLayer` context, or `null` if the layer has not yet been added.

#### Returns

`CustomLayer | null | undefined` — `null` while the layer is being set up; the `CustomLayer` instance once added; `undefined` if called outside a `MapboxCustomLayer` provider.

#### Example

```typescript
const customLayer = useCustomLayer()

useEffect(() => {
    if (customLayer) {
        const mesh = new Mesh(geometry, material)
        customLayer.add(mesh)
        return () => customLayer.remove(mesh)
    }
}, [customLayer])
```
