|
|
# 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
|
|
|
```
|
|
|
|
|
|
## Import options
|
|
|
|
|
|
The package is ESM-only — `tsc` emits `dist/` as individual modules, one per component, with no bundling step. Deep imports work for registering only what you use.
|
|
|
|
|
|
```js
|
|
|
// Registers all components
|
|
|
import 'leaflet-components';
|
|
|
|
|
|
// Deep import — register only one component
|
|
|
import 'leaflet-components/dist/components/leaflet-marker.js';
|
|
|
```
|
|
|
|
|
|
`leaflet` is always an external dependency — you must install it yourself. There is no CommonJS or UMD build.
|
|
|
|
|
|
## 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></leaflet-map>
|
|
|
```
|
|
|
|
|
|
### 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.leafletObject; // 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.
|
|
|
|
|
|
## Escape hatch
|
|
|
|
|
|
Every component exposes a `leafletObject` getter that returns the underlying Leaflet instance. This is useful when you need to call Leaflet API methods directly:
|
|
|
|
|
|
```js
|
|
|
const marker = document.querySelector('leaflet-marker');
|
|
|
marker.leafletObject?.setLatLng([51.5, -0.09]);
|
|
|
|
|
|
const map = document.querySelector('leaflet-map');
|
|
|
map.leafletObject?.flyTo([48.86, 2.35], 13);
|
|
|
```
|
|
|
|
|
|
Returns `undefined` while the element isn't connected to the DOM.
|
|
|
|
|
|
### CSS
|
|
|
|
|
|
Leaflet's stylesheet is loaded from a CDN via a `<link>` in the shadow DOM. Marker icon paths are derived from the CSS URL automatically.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ----------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
|
| `css-url` | `https://unpkg.com/leaflet@1.9.4/dist/leaflet.css` | URL for Leaflet CSS |
|
|
|
| `css-integrity` | `sha256-...` | SRI hash (required with default URL; when `css-url` is custom, no default integrity is used unless explicitly set) |
|
|
|
| `css-crossorigin` | _(auto)_ | `"anonymous"` when integrity is active, otherwise absent. Set explicitly to override. |
|
|
|
|
|
|
### Resize
|
|
|
|
|
|
A `ResizeObserver` on the host element automatically calls `map.invalidateSize()` whenever the element's dimensions change — from CSS classes, inline styles, attribute changes, or parent layout.
|
|
|
|
|
|
## Events
|
|
|
|
|
|
Every event the underlying Leaflet object fires is re-emitted on the element as a `leaflet:<type>` DOM event — `zoomend` becomes `leaflet:zoomend`, `popupopen` becomes `leaflet:popupopen`, and so on. This is generic and automatic for every component; nothing needs to be configured per event.
|
|
|
|
|
|
```js
|
|
|
const map = document.querySelector('leaflet-map');
|
|
|
map.addEventListener('leaflet:moveend', (e) => {
|
|
|
console.log(e.detail.target.getCenter());
|
|
|
});
|
|
|
|
|
|
const marker = document.querySelector('leaflet-marker');
|
|
|
marker.addEventListener('leaflet:dragend', (e) => {
|
|
|
console.log('dragged', e.detail.distance, 'px');
|
|
|
});
|
|
|
```
|
|
|
|
|
|
`event.detail` is the same object a native Leaflet `.on()` listener receives — it always carries `type`, `target`, and `sourceTarget` in addition to whatever fields that particular event adds (`latlng` for mouse events, `popup` for `popupopen`/`popupclose`, `distance` for `dragend`, etc.). These events don't bubble: Leaflet already propagates layer events up to the map internally, so a bubbling DOM event would make `<leaflet-map>` see each one twice.
|
|
|
|
|
|
Which events a component can fire depends on the Leaflet class it wraps — see [Leaflet's own event reference](https://leafletjs.com/reference.html) for the full list per class (Map, Layer, Marker, Path, Popup, TileLayer, etc.).
|
|
|
|
|
|
## TypeScript
|
|
|
|
|
|
`leaflet-*` elements are typed. `document.createElement`/`querySelector` infer the right component class for every tag:
|
|
|
|
|
|
```ts
|
|
|
const map = document.querySelector('leaflet-map'); // LeafletMap | null
|
|
|
```
|
|
|
|
|
|
`addEventListener` is typed per component against the real Leaflet event payload for events that component actually fires — ordinary DOM events (`click`, etc.) still work normally, and an event name the component doesn't fire is a compile error:
|
|
|
|
|
|
```ts
|
|
|
marker.addEventListener('leaflet:dragend', (e) => {
|
|
|
e.detail.distance; // number — DragEndEvent
|
|
|
});
|
|
|
|
|
|
// Type error: baselayerchange is only fired by <leaflet-map>, not <leaflet-marker>.
|
|
|
marker.addEventListener('leaflet:baselayerchange', () => {});
|
|
|
```
|
|
|
|
|
|
There's no generic fallback overload for `leaflet:*` names — that's deliberate, so a typo or a wrong event name doesn't silently type-check. A genuinely dynamic (non-literal) event name string needs a cast.
|
|
|
|
|
|
---
|
|
|
|
|
|
## Components
|
|
|
|
|
|
### `<leaflet-tile-layer>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
|
| `url` | `''` | Tile URL template (`https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`) |
|
|
|
| `attribution` | `''` | Attribution text |
|
|
|
| `min-zoom` | `0` | Minimum zoom level |
|
|
|
| `max-zoom` | `18` | Maximum zoom level |
|
|
|
| `opacity` | `1.0` | Tile layer opacity |
|
|
|
| `z-index` | `1` | Z-index |
|
|
|
| `subdomains` | `'abc'` | Subdomains for `{s}` in the URL template |
|
|
|
| `tms` | — | Use TMS tile coordinate scheme |
|
|
|
| `zoom-offset` | `0` | Offset added to the zoom level when requesting tiles |
|
|
|
| `zoom-reverse` | — | Reverse the zoom level when requesting tiles |
|
|
|
| `detect-retina` | — | Request higher-resolution tiles on retina displays |
|
|
|
| `cross-origin` | `''` | CORS setting for tile images |
|
|
|
| `referrer-policy` | — | `referrerPolicy` for tile image requests |
|
|
|
| `error-tile-url` | `''` | Fallback tile image on load error |
|
|
|
| `tile-size` | `256` | Tile size in pixels |
|
|
|
| `no-wrap` | — | Don't wrap tiles horizontally across the antimeridian |
|
|
|
| `bounds` | `''` | Restrict tile loading to this bounding box, as JSON: `[[south,west],[north,east]]` |
|
|
|
| `class-name` | `''` | CSS class for tile images |
|
|
|
| `min-native-zoom` | — | Lowest zoom level the tile source natively supports |
|
|
|
| `max-native-zoom` | — | Highest zoom level the tile source natively supports |
|
|
|
| `keep-buffer` | `2` | Extra rows/columns of tiles to keep loaded outside the viewport |
|
|
|
| `update-when-idle` | — | Update tiles only when the map stops moving |
|
|
|
| `update-when-zooming` | `true` (toggle with `update-when-zooming="false"`) | Update tiles continuously while zooming |
|
|
|
| `update-interval` | `200` | Throttle between tile updates while panning (ms) |
|
|
|
| `pane` | `'tilePane'` | Map pane name |
|
|
|
|
|
|
Most of these apply only at construction — Leaflet exposes no setter for them, so changing the attribute afterward has no effect. `min-zoom`, `max-zoom`, `opacity`, and `z-index` are live.
|
|
|
|
|
|
### `<leaflet-tile-layer-wms>`
|
|
|
|
|
|
All `<leaflet-tile-layer>` attributes above apply (a WMS layer is a tile layer), plus:
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ------------- | -------------- | -------------------------------------------------------------------------------------------- |
|
|
|
| `url` | `''` | WMS service URL |
|
|
|
| `layers` | `''` | Comma-separated layer names |
|
|
|
| `styles` | `''` | Comma-separated style names |
|
|
|
| `format` | `'image/jpeg'` | Image format |
|
|
|
| `transparent` | — | Request transparent tiles |
|
|
|
| `version` | `'1.1.1'` | WMS version |
|
|
|
| `uppercase` | — | Use uppercase WMS parameter names |
|
|
|
| `crs` | — | Coordinate reference system by name: `'EPSG3857'`, `'EPSG4326'`, `'EPSG3395'`, or `'Simple'` |
|
|
|
|
|
|
### `<leaflet-marker>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ---------------- | ------- | ----------------------------- |
|
|
|
| `lat` | `0` | Latitude |
|
|
|
| `lng` | `0` | Longitude |
|
|
|
| `title` | `''` | Tooltip text on hover |
|
|
|
| `alt` | `''` | Alt text for the marker image |
|
|
|
| `draggable` | — | Allow dragging the marker |
|
|
|
| `opacity` | `1.0` | Marker opacity |
|
|
|
| `z-index-offset` | `0` | Z-index offset |
|
|
|
|
|
|
A `<leaflet-icon>` or `<leaflet-div-icon>` child sets the marker's icon (see below); without one, Leaflet's default icon is used.
|
|
|
|
|
|
### `<leaflet-icon>`
|
|
|
|
|
|
Sets the marker icon to an image. Not rendered itself — swapped into a parent `<leaflet-marker>` via `setIcon()` whenever an attribute changes. Produces no icon (Leaflet's default is used instead) until `icon-url` is set.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ------------------- | ------- | --------------------------------------------------------------------- |
|
|
|
| `icon-url` | `''` | Icon image URL (required) |
|
|
|
| `icon-retina-url` | `''` | Higher-resolution icon image URL |
|
|
|
| `icon-size` | — | Icon size as JSON: `[width, height]` |
|
|
|
| `icon-anchor` | — | Point of the icon aligned to the marker's location, as JSON: `[x, y]` |
|
|
|
| `popup-anchor` | — | Popup anchor point relative to `icon-anchor`, as JSON: `[x, y]` |
|
|
|
| `tooltip-anchor` | — | Tooltip anchor point relative to `icon-anchor`, as JSON: `[x, y]` |
|
|
|
| `shadow-url` | `''` | Shadow image URL |
|
|
|
| `shadow-retina-url` | `''` | Higher-resolution shadow image URL |
|
|
|
| `shadow-size` | — | Shadow size as JSON: `[width, height]` |
|
|
|
| `shadow-anchor` | — | Shadow anchor point, as JSON: `[x, y]` |
|
|
|
| `class-name` | `''` | CSS class for the icon image |
|
|
|
|
|
|
### `<leaflet-div-icon>`
|
|
|
|
|
|
Sets the marker icon to an HTML element instead of an image. Content comes from `innerHTML` (or the `html` attribute if no markup is given) — either way it's rebuilt on every change, the same as `<leaflet-icon>`.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ---------------- | -------------------- | --------------------------------------------------------------------- |
|
|
|
| `icon-size` | — | Icon size as JSON: `[width, height]` |
|
|
|
| `icon-anchor` | — | Point of the icon aligned to the marker's location, as JSON: `[x, y]` |
|
|
|
| `popup-anchor` | — | Popup anchor point relative to `icon-anchor`, as JSON: `[x, y]` |
|
|
|
| `tooltip-anchor` | — | Tooltip anchor point relative to `icon-anchor`, as JSON: `[x, y]` |
|
|
|
| `class-name` | `'leaflet-div-icon'` | CSS class for the icon element |
|
|
|
| `html` | `''` | HTML content, if not using `innerHTML` |
|
|
|
| `bg-pos` | — | Background position offset, as JSON: `[x, y]` |
|
|
|
|
|
|
### `<leaflet-circle>`
|
|
|
|
|
|
All [path style](#path-style) attributes apply.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------- | ------- | ---------------- |
|
|
|
| `lat` | `0` | Center latitude |
|
|
|
| `lng` | `0` | Center longitude |
|
|
|
| `radius` | `1000` | Radius in meters |
|
|
|
|
|
|
### `<leaflet-circle-marker>`
|
|
|
|
|
|
All [path style](#path-style) attributes apply.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------- | ------- | ---------------- |
|
|
|
| `lat` | `0` | Center latitude |
|
|
|
| `lng` | `0` | Center longitude |
|
|
|
| `radius` | `10` | Radius in pixels |
|
|
|
|
|
|
### `<leaflet-polyline>`
|
|
|
|
|
|
Coordinates are taken from child `<leaflet-line>` elements, not from attributes. All [path style](#path-style) attributes apply, except `fill` defaults to unfilled (a polyline isn't a closed shape, but it can still be filled explicitly).
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------------- | ------- | ------------------------------------------------------------------------------- |
|
|
|
| `smooth-factor` | `1.0` | Simplification factor applied while panning/zooming — higher is more simplified |
|
|
|
| `no-clip` | — | Disable polyline clipping |
|
|
|
|
|
|
### `<leaflet-polygon>`
|
|
|
|
|
|
Coordinates are taken from child `<leaflet-line>` elements, not from attributes. All [path style](#path-style) attributes apply.
|
|
|
|
|
|
### `<leaflet-rectangle>`
|
|
|
|
|
|
All [path style](#path-style) attributes apply.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------- | ------- | --------------------------------------------------- |
|
|
|
| `bounds` | `''` | Bounding box as JSON: `[[south,west],[north,east]]` |
|
|
|
|
|
|
### `<leaflet-line>`
|
|
|
|
|
|
Vertex helper — not rendered directly. Used as a child of `<leaflet-polyline>` or `<leaflet-polygon>`.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------- | ------- | ----------- |
|
|
|
| `lat` | `0` | Latitude |
|
|
|
| `lng` | `0` | Longitude |
|
|
|
|
|
|
### `<leaflet-image-overlay>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ------------------- | ------- | --------------------------------------------------- |
|
|
|
| `url` | `''` | Image URL |
|
|
|
| `bounds` | `''` | Bounding box as JSON: `[[south,west],[north,east]]` |
|
|
|
| `opacity` | `1.0` | Overlay opacity |
|
|
|
| `alt` | `''` | Alt text |
|
|
|
| `interactive` | — | Receive mouse/touch events |
|
|
|
| `cross-origin` | `''` | CORS setting for the image |
|
|
|
| `error-overlay-url` | `''` | Fallback image on load error |
|
|
|
| `z-index` | `0` | Z-index |
|
|
|
| `class-name` | `''` | CSS class for the image element |
|
|
|
|
|
|
### `<leaflet-video-overlay>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ------------------- | ------------------------------------------------ | --------------------------------------------------- |
|
|
|
| `url` | `''` | Video URL |
|
|
|
| `bounds` | `''` | Bounding box as JSON: `[[south,west],[north,east]]` |
|
|
|
| `opacity` | `1.0` | Overlay opacity |
|
|
|
| `alt` | `''` | Alt text |
|
|
|
| `interactive` | — | Receive mouse/touch events |
|
|
|
| `cross-origin` | `''` | CORS setting for the video |
|
|
|
| `loop` | — | Loop playback |
|
|
|
| `autoplay` | — | Start playing automatically |
|
|
|
| `muted` | — | Mute audio |
|
|
|
| `playsinline` | — | Play inline (mobile) |
|
|
|
| `z-index` | `0` | Z-index |
|
|
|
| `class-name` | `''` | CSS class for the video element |
|
|
|
| `keep-aspect-ratio` | `true` (toggle with `keep-aspect-ratio="false"`) | Preserve the video's aspect ratio within its bounds |
|
|
|
| `error-overlay-url` | `''` | Fallback image on load error |
|
|
|
|
|
|
### `<leaflet-svg-overlay>`
|
|
|
|
|
|
Content comes from an inline `<svg>` child element. No `url` attribute.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| -------------- | ------- | --------------------------------------------------- |
|
|
|
| `bounds` | `''` | Bounding box as JSON: `[[south,west],[north,east]]` |
|
|
|
| `opacity` | `1.0` | Overlay opacity |
|
|
|
| `interactive` | — | Receive mouse/touch events |
|
|
|
| `cross-origin` | `''` | CORS setting |
|
|
|
| `z-index` | `0` | Z-index |
|
|
|
| `class-name` | `''` | CSS class for the SVG element |
|
|
|
|
|
|
### `<leaflet-layer-group>`
|
|
|
|
|
|
Passthrough container. No attributes. Accepts layers, popups, and tooltips as children.
|
|
|
|
|
|
### `<leaflet-feature-group>`
|
|
|
|
|
|
Passthrough container with Leaflet's `FeatureGroup` (supports `getBounds()`, style propagation, etc.). No attributes. Accepts layers, popups, and tooltips as children.
|
|
|
|
|
|
### `<leaflet-geojson>`
|
|
|
|
|
|
All [path style](#path-style) attributes apply — each feature GeoJSON builds gets them as its style.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| --------- | ------- | -------------- |
|
|
|
| `data` | `''` | GeoJSON string |
|
|
|
|
|
|
### `<leaflet-popup>`
|
|
|
|
|
|
Content comes from `innerHTML`, not an attribute. Mutations to innerHTML sync automatically via a `MutationObserver`.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| -------------- | ------- | ------------------------------------------------------------- |
|
|
|
| `lat` | `0` | Latitude (omit to auto-attach to the parent layer's position) |
|
|
|
| `lng` | `0` | Longitude |
|
|
|
| `max-width` | `300` | Maximum width (px) |
|
|
|
| `min-width` | `50` | Minimum width (px) |
|
|
|
| `max-height` | `0` | Maximum height (px; `0` = unlimited) |
|
|
|
| `auto-pan` | — | Automatically pan the map to keep the popup visible |
|
|
|
| `close-button` | — | Show a close button |
|
|
|
| `auto-close` | — | Close the popup when another popup is opened |
|
|
|
|
|
|
### `<leaflet-tooltip>`
|
|
|
|
|
|
Content comes from `innerHTML`, not an attribute.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ----------- | -------- | ------------------------------------------------------------------------- |
|
|
|
| `lat` | `0` | Latitude (omit to auto-attach to the parent layer's position) |
|
|
|
| `lng` | `0` | Longitude |
|
|
|
| `pane` | — | Map pane name |
|
|
|
| `offset` | — | Pixel offset as JSON: `[x, y]` |
|
|
|
| `direction` | `'auto'` | Direction: `'right'`, `'left'`, `'top'`, `'bottom'`, `'center'`, `'auto'` |
|
|
|
| `permanent` | — | Always visible (no hover required) |
|
|
|
| `sticky` | — | Follow the mouse |
|
|
|
| `opacity` | `1.0` | Tooltip opacity |
|
|
|
|
|
|
### `<leaflet-control-zoom>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ---------------- | ------------ | ----------------------- |
|
|
|
| `position` | `'topleft'` | Corner position |
|
|
|
| `zoom-in-text` | `'+'` | Zoom-in button label |
|
|
|
| `zoom-in-title` | `'Zoom in'` | Zoom-in button tooltip |
|
|
|
| `zoom-out-text` | `'-'` | Zoom-out button label |
|
|
|
| `zoom-out-title` | `'Zoom out'` | Zoom-out button tooltip |
|
|
|
|
|
|
### `<leaflet-control-attribution>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ---------- | --------------- | --------------------------- |
|
|
|
| `position` | `'bottomright'` | Corner position |
|
|
|
| `prefix` | `''` | Text before the attribution |
|
|
|
|
|
|
### `<leaflet-control-scale>`
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ------------------ | -------------- | ------------------------------------- |
|
|
|
| `position` | `'bottomleft'` | Corner position |
|
|
|
| `max-width` | `100` | Maximum width of the scale (px) |
|
|
|
| `metric` | — | Show metric scale (m/km) |
|
|
|
| `imperial` | — | Show imperial scale (mi/ft) |
|
|
|
| `update-when-idle` | — | Update only when the map stops moving |
|
|
|
|
|
|
### `<leaflet-control-layers>`
|
|
|
|
|
|
The built-in Leaflet layer switcher. Children with `type="base"` appear as radio buttons; children with `type="overlay"` (or no `type`) appear as checkboxes.
|
|
|
|
|
|
```html
|
|
|
<leaflet-map>
|
|
|
<leaflet-control-layers position="topright" collapsed>
|
|
|
<leaflet-tile-layer type="base" name="Streets" active url="..."></leaflet-tile-layer>
|
|
|
<leaflet-tile-layer type="base" name="Satellite" url="..."></leaflet-tile-layer>
|
|
|
<leaflet-marker type="overlay" name="Cities" active lat="51.5" lng="-0.09"></leaflet-marker>
|
|
|
</leaflet-control-layers>
|
|
|
</leaflet-map>
|
|
|
```
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ------------------ | ---------------------------------------- | ------------------------------------------------------------ |
|
|
|
| `position` | `'topright'` | Corner position |
|
|
|
| `collapsed` | `true` (toggle with `collapsed="false"`) | Collapse into an icon until hovered |
|
|
|
| `auto-z-index` | `true` | Assign increasing z-indexes to layers |
|
|
|
| `hide-single-base` | — | Hide the base layers section when only one base layer exists |
|
|
|
| `sort-layers` | — | Sort layers alphabetically |
|
|
|
|
|
|
Each child layer inside `<leaflet-control-layers>` may carry:
|
|
|
|
|
|
| Attribute | Description |
|
|
|
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| `type="base"` | Show as a radio button (base layer). Omit or set `type="overlay"` for a checkbox. |
|
|
|
| `name` | Human-readable label displayed in the control. |
|
|
|
| `active` | Add the layer to the map immediately so it starts visible. Internally, the control dispatches a `leaflet-add-layer` event that the map handles. Layers without `active` are removed from the map before the control renders, so they correctly appear unchecked. |
|
|
|
|
|
|
The control intercepts child `leaflet-register` events and stops propagation — layers inside `<leaflet-control-layers>` are managed by the layers control, not added directly to the map.
|
|
|
|
|
|
## Path style
|
|
|
|
|
|
The following attributes apply to `<leaflet-circle>`, `<leaflet-circle-marker>`, `<leaflet-polyline>`, `<leaflet-polygon>`, `<leaflet-rectangle>`, and `<leaflet-geojson>`.
|
|
|
|
|
|
| Attribute | Default | Description |
|
|
|
| ----------------------- | ---------------------------------------------------- | ------------------------------------------------- |
|
|
|
| `stroke` | `true` (toggle with `stroke="false"`) | Draw the stroke |
|
|
|
| `color` | `'#3388ff'` | Stroke color |
|
|
|
| `weight` | `3` | Stroke width (px) |
|
|
|
| `opacity` | `1.0` | Stroke opacity |
|
|
|
| `line-cap` | `'round'` | Line cap: `'butt'`, `'round'`, `'square'` |
|
|
|
| `line-join` | `'round'` | Line join: `'miter'`, `'round'`, `'bevel'` |
|
|
|
| `dash-array` | `''` | Dash pattern, e.g. `'5, 10'` |
|
|
|
| `dash-offset` | `''` | Dash offset |
|
|
|
| `fill` | `true` (toggle with `fill="false"`) | Enable fill |
|
|
|
| `fill-color` | `'#3388ff'` | Fill color |
|
|
|
| `fill-opacity` | `0.2` | Fill opacity |
|
|
|
| `fill-rule` | `'evenodd'` | Fill rule: `'nonzero'`, `'evenodd'` |
|
|
|
| `interactive` | `true` (toggle with `interactive="false"`) | Receive mouse/touch events |
|
|
|
| `bubbling-mouse-events` | `true` (toggle with `bubbling-mouse-events="false"`) | Let mouse events on this layer also reach the map |
|
|
|
| `class-name` | `''` | CSS class for the path element |
|
|
|
| `pane` | `'overlay'` | Map pane name |
|
|
|
|
|
|
`stroke`, `color`, `weight`, `opacity`, `line-cap`, `line-join`, `dash-array`, `dash-offset`, `fill`, `fill-color`, `fill-opacity`, and `fill-rule` are live — Leaflet has a setter for them (`setStyle`). `interactive`, `bubbling-mouse-events`, `class-name`, and `pane` only apply at construction.
|
|
|
|
|
|
## 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.
|
|
|
- **`<leaflet-icon>` / `<leaflet-div-icon>` as child of `<leaflet-marker>`** → swapped in via `setIcon()`.
|
|
|
- **Layer as child of `<leaflet-control-layers>`** → intercepted by the control and registered as a base or overlay entry. The control stops propagation so the layer doesn't reach the map directly. Use `active` to start the layer visible.
|
|
|
|
|
|
## Development
|
|
|
|
|
|
```bash
|
|
|
npm run build # tsc emits individual ESM modules to dist/ (no bundling)
|
|
|
npm run typecheck # tsc --noEmit, then tsc -p tsconfig.test.json for test/
|
|
|
npm run lint # oxlint over src/ and test/
|
|
|
npm run format # Prettier
|
|
|
npm run test # vitest run
|
|
|
npm run test:watch # vitest, watch mode
|
|
|
# serve the project root with any static-file server and open index.html
|
|
|
```
|