|
|
# leaflet-components
|
|
|
|
|
|
[Leaflet.js](https://leafletjs.com/) as native Web Components (Custom Elements). Each HTML element maps 1:1 to a Leaflet object with reactive attribute binding.
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
```bash
|
|
|
npm install leaflet-components
|
|
|
```
|
|
|
|
|
|
Available on JSR as `@buddy/leaflet-components`.
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
Import once to register all custom elements, then use them declaratively in HTML.
|
|
|
|
|
|
```html
|
|
|
<script type="module">
|
|
|
import 'leaflet-components';
|
|
|
</script>
|
|
|
|
|
|
<leaflet-map lat="51.505" lng="-0.09" zoom="13" style="height:400px">
|
|
|
<leaflet-tile-layer
|
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
|
attribution="© OpenStreetMap contributors"
|
|
|
></leaflet-tile-layer>
|
|
|
|
|
|
<leaflet-marker lat="51.505" lng="-0.09">
|
|
|
<leaflet-popup><b>Hello!</b></leaflet-popup>
|
|
|
<leaflet-tooltip>Hover me</leaflet-tooltip>
|
|
|
</leaflet-marker>
|
|
|
|
|
|
<leaflet-circle lat="51.508" lng="-0.11" radius="500" color="red" fill-color="#f03" fill-opacity="0.5">
|
|
|
<leaflet-popup>I am a circle</leaflet-popup>
|
|
|
</leaflet-circle>
|
|
|
|
|
|
<leaflet-polygon color="blue">
|
|
|
<leaflet-line lat="51.509" lng="-0.08"></leaflet-line>
|
|
|
<leaflet-line lat="51.503" lng="-0.06"></leaflet-line>
|
|
|
<leaflet-line lat="51.510" lng="-0.047"></leaflet-line>
|
|
|
</leaflet-polygon>
|
|
|
|
|
|
<leaflet-control-scale position="bottomleft"></leaflet-control-scale>
|
|
|
</leaflet-map>
|
|
|
```
|
|
|
|
|
|
## `leaflet-map`
|
|
|
|
|
|
The root component. All other components must be children of `<leaflet-map>`.
|
|
|
|
|
|
```html
|
|
|
<leaflet-map lat="51.505" lng="-0.09" zoom="13" disable-scroll-wheel-zoom>
|
|
|
```
|
|
|
|
|
|
### View state
|
|
|
|
|
|
These attributes stay in sync with the map as the user interacts with it — panning updates `lat`/`lng`, zooming updates `zoom`.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `lat` | `0` | Center latitude |
|
|
|
| `lng` | `0` | Center longitude |
|
|
|
| `zoom` | `2` | Zoom level |
|
|
|
| `min-zoom` | `0` | Minimum zoom level |
|
|
|
| `max-zoom` | — | Maximum zoom level. When unset, Leaflet uses the tile layer's own max zoom. |
|
|
|
|
|
|
### Interaction
|
|
|
|
|
|
Boolean options that default to `true` are controlled by the presence of a `disable-*` attribute. Handler-based options can be toggled at any time; the rest only apply at construction.
|
|
|
|
|
|
| Attribute | Controls | Live? |
|
|
|
|---|---|---|
|
|
|
| `disable-dragging` | Mouse/touch panning | ✓ |
|
|
|
| `disable-scroll-wheel-zoom` | Scroll-wheel zoom | ✓ |
|
|
|
| `disable-double-click-zoom` | Double-click zoom | ✓ |
|
|
|
| `disable-touch-zoom` | Pinch-to-zoom | ✓ |
|
|
|
| `disable-box-zoom` | Shift-drag zoom box | ✓ |
|
|
|
| `disable-keyboard` | Keyboard pan/zoom | ✓ |
|
|
|
| `disable-zoom-control` | Built-in zoom control | — |
|
|
|
| `disable-attribution-control` | Built-in attribution | — |
|
|
|
| `disable-close-popup-on-click` | Close popup on map click | — |
|
|
|
| `disable-track-resize` | Auto-resize on window resize | — |
|
|
|
| `disable-bounce-at-zoom-limits` | Bounce animation at min/max zoom | — |
|
|
|
| `disable-tap-hold` | Long-press context menu (mobile) | — |
|
|
|
|
|
|
Boolean options that default to `false` are enabled by adding the attribute:
|
|
|
|
|
|
| Attribute | Description |
|
|
|
|---|---|
|
|
|
| `prefer-canvas` | Render vector layers on Canvas instead of SVG |
|
|
|
| `world-copy-jump` | Pan to the original world copy when crossing the antimeridian |
|
|
|
|
|
|
### Animation
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `disable-zoom-animation` | — | Disable CSS zoom animation |
|
|
|
| `disable-fade-animation` | — | Disable tile fade-in |
|
|
|
| `disable-marker-zoom-animation` | — | Disable marker zoom animation |
|
|
|
| `zoom-animation-threshold` | `4` | Max zoom delta for animated zoom |
|
|
|
|
|
|
### Inertia & panning
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `disable-inertia` | — | Disable inertial panning |
|
|
|
| `inertia-deceleration` | `3000` | Deceleration rate (px/s²) |
|
|
|
| `inertia-max-speed` | `Infinity` | Maximum inertia speed (px/s) |
|
|
|
| `ease-linearity` | `0.2` | Pan easing linearity |
|
|
|
|
|
|
### Zoom behaviour
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `zoom-snap` | `1` | Zoom snapping interval; `0` for continuous zoom |
|
|
|
| `zoom-delta` | `1` | Zoom step per keyboard/button press |
|
|
|
| `max-bounds-viscosity` | `0` | How much the bounds resist panning past them (`0`–`1`) |
|
|
|
|
|
|
### Scroll wheel
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `wheel-debounce-time` | `40` | Debounce delay for wheel events (ms) |
|
|
|
| `wheel-px-per-zoom-level` | `60` | Pixels of scroll per zoom level |
|
|
|
|
|
|
### Keyboard & touch
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `keyboard-pan-delta` | `80` | Pan distance per key press (px) |
|
|
|
| `tap-tolerance` | `15` | Max touch movement to trigger a tap (px) |
|
|
|
|
|
|
### Rendering
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
|---|---|---|
|
|
|
| `transform-3d-limit` | `8388608` | Max CSS `translate3d` component value before a layer reset |
|
|
|
|
|
|
### Accessing the underlying map
|
|
|
|
|
|
```js
|
|
|
const el = document.querySelector('leaflet-map');
|
|
|
el.map; // L.Map instance (undefined before connected)
|
|
|
el.zoom; // current zoom (reads live from map, falls back to attribute)
|
|
|
el.scrollWheelZoom = false; // disable at runtime
|
|
|
```
|
|
|
|
|
|
All `leaflet-map` properties are two-way: reading returns the live map value; writing updates both the attribute and the map.
|
|
|
|
|
|
---
|
|
|
|
|
|
## Other components
|
|
|
|
|
|
### Tile layers
|
|
|
|
|
|
| Element | Key attributes |
|
|
|
|---|---|
|
|
|
| `leaflet-tile-layer` | `url`, `attribution`, `min-zoom`, `max-zoom`, `opacity`, `z-index` |
|
|
|
| `leaflet-tile-layer-wms` | `url`, `layers`, `styles`, `format`, `transparent`, `version` |
|
|
|
|
|
|
### Vector layers
|
|
|
|
|
|
Path style attributes (`color`, `weight`, `opacity`, `fill`, `fill-color`, `fill-opacity`, `dash-array`, `line-cap`, `line-join`) are shared by all vector layers.
|
|
|
|
|
|
| Element | Additional attributes | Notes |
|
|
|
|---|---|---|
|
|
|
| `leaflet-circle` | `lat`, `lng`, `radius` (meters) | |
|
|
|
| `leaflet-circle-marker` | `lat`, `lng`, `radius` (pixels) | |
|
|
|
| `leaflet-polyline` | — | Vertices via `<leaflet-line>` children |
|
|
|
| `leaflet-polygon` | — | Vertices via `<leaflet-line>` children |
|
|
|
| `leaflet-rectangle` | `bounds` (JSON) | |
|
|
|
| `leaflet-line` | `lat`, `lng` | Vertex only — not rendered directly |
|
|
|
|
|
|
### Markers & overlays
|
|
|
|
|
|
| Element | Key attributes |
|
|
|
|---|---|
|
|
|
| `leaflet-marker` | `lat`, `lng`, `title`, `alt`, `draggable`, `opacity`, `z-index-offset` |
|
|
|
| `leaflet-image-overlay` | `url`, `bounds` (JSON), `opacity`, `alt`, `interactive` |
|
|
|
| `leaflet-video-overlay` | `url`, `bounds` (JSON), `opacity`, `loop`, `autoplay`, `muted` |
|
|
|
| `leaflet-svg-overlay` | `bounds` (JSON), `opacity`, `interactive` — wrap an `<svg>` element |
|
|
|
|
|
|
### Groups & GeoJSON
|
|
|
|
|
|
| Element | Key attributes |
|
|
|
|---|---|
|
|
|
| `leaflet-layer-group` | — |
|
|
|
| `leaflet-feature-group` | — |
|
|
|
| `leaflet-geojson` | `data` (GeoJSON string), plus path style attributes |
|
|
|
|
|
|
### UI layers
|
|
|
|
|
|
Content is set via `innerHTML`, not attributes.
|
|
|
|
|
|
| Element | Key attributes |
|
|
|
|---|---|
|
|
|
| `leaflet-popup` | `lat`, `lng`, `max-width`, `min-width`, `auto-pan`, `close-button`, `auto-close` |
|
|
|
| `leaflet-tooltip` | `lat`, `lng`, `direction`, `permanent`, `sticky`, `opacity` |
|
|
|
|
|
|
### Controls
|
|
|
|
|
|
| Element | Key attributes |
|
|
|
|---|---|
|
|
|
| `leaflet-control-zoom` | `position`, `zoom-in-text`, `zoom-out-text` |
|
|
|
| `leaflet-control-attribution` | `position`, `prefix` |
|
|
|
| `leaflet-control-scale` | `position`, `max-width`, `metric`, `imperial` |
|
|
|
|
|
|
## Nesting rules
|
|
|
|
|
|
- **Popup / tooltip as child of a layer** → bound via `bindPopup` / `bindTooltip`.
|
|
|
- **Layer as child of a group** → added via `addLayer`.
|
|
|
- **Any layer as child of `leaflet-map`** → added to the map directly.
|
|
|
- **`leaflet-polygon` / `leaflet-polyline`** take their coordinates from `<leaflet-line>` children, not from attributes.
|
|
|
|
|
|
## Development
|
|
|
|
|
|
```bash
|
|
|
npm run build # type-check + emit .d.ts via tsc, then bundle to dist/index.js via esbuild
|
|
|
npm run lint # ESLint
|
|
|
npm run format # Prettier
|
|
|
npx vite # open index.html for a live demo
|
|
|
```
|