You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
Buddy 96b48a6ae2 refactor: extract #parsedBounds into parseBoundsAttr helper
Replaces the identical private #parsedBounds method in rectangle,
image-overlay, video-overlay, and svg-overlay with a call to
parseBoundsAttr(el) from utils.ts. Removes 4 x 5 = 20 lines
of method definitions.
3 months ago
src refactor: extract #parsedBounds into parseBoundsAttr helper 3 months ago
.gitignore feat: Initial implementation of leaflet-components 4 months ago
CLAUDE.md feat: Initial implementation of leaflet-components 4 months ago
README.md docs: add full attribute tables for all components 4 months ago
eslint.config.js feat: Initial implementation of leaflet-components 4 months ago
index.html feat: group child tags by type in add-child wizard 3 months ago
jsr.json feat: Initial implementation of leaflet-components 4 months ago
package-lock.json feat: Initial implementation of leaflet-components 4 months ago
package.json feat: load Leaflet CSS via CDN <link> instead of bundling 4 months ago
prettier.config.js core: rename prettier config and run prettier 3 months ago
tsconfig.json feat: Initial implementation of leaflet-components 4 months ago

README.md

leaflet-components

Leaflet.js as native Web Components (Custom Elements). Each HTML element maps 1:1 to a Leaflet object with reactive attribute binding.

Installation

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.

<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>.

<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 (01)

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

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.

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.


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 0 Z-index

<leaflet-tile-layer-wms>

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

<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

<leaflet-circle>

All 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 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 attributes apply (excluding fill* — polylines don't fill).

Attribute Default Description
color '#3388ff' Stroke color
weight 3 Stroke width (px)
opacity 1.0 Stroke opacity

<leaflet-polygon>

Coordinates are taken from child <leaflet-line> elements, not from attributes. All path style attributes apply.

<leaflet-rectangle>

All 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)

<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 attributes apply.

Attribute Default Description
data '' GeoJSON string
stroke '' Stroke color (overrides color)
line-cap 'round' Line cap style
line-join 'round' Line join style
dash-array '' Dash pattern
dash-offset '' Dash offset
fill-rule 'evenodd' Fill rule

<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

Path style

The following attributes apply to <leaflet-circle>, <leaflet-circle-marker>, <leaflet-polyline>, <leaflet-polygon>, <leaflet-rectangle>, and <leaflet-geojson>.

Attribute Default Description
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 Enable fill
fill-color '#3388ff' Fill color
fill-opacity 0.2 Fill opacity
fill-rule 'evenodd' Fill rule: 'nonzero', 'evenodd'

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

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