# 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 Hello! Hover me I am a circle ``` ## `leaflet-map` The root component. All other components must be children of ``. ```html ``` ### 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 `` children | | `leaflet-polygon` | — | Vertices via `` 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 `` 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 `` 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 ```