npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@classic-homes/maps-svelte

v0.2.12

Published

Svelte MapLibre components for Classic Theme

Downloads

188

Readme

@classic-homes/maps-svelte

Svelte 5 components for MapLibre GL maps in the Classic Theme design system.

Installation

npm install @classic-homes/maps-svelte maplibre-gl

Features

  • Svelte 5 Runes: Modern $state, $derived, $effect patterns
  • 7 Layer Types: Fill, Line, Circle, Symbol, FillExtrusion, Heatmap, Raster
  • Controls: Navigation, Scale, Geolocation, Legend, Coordinate Display
  • Clustering: Built-in cluster support with expansion utilities
  • Deck.gl Integration: Optional overlay for advanced visualizations
  • Accessibility: ARIA labels, keyboard navigation, reduced motion support
  • Theme Integration: Light/dark themes from design tokens

Quick Start

<script lang="ts">
  import { Map, MapContainer, Source, FillLayer } from '@classic-homes/maps-svelte';
  import 'maplibre-gl/dist/maplibre-gl.css';

  let geojsonData = $state(/* your data */);
</script>

<MapContainer class="h-[500px]">
  <Map
    initialCenter={[-122.4, 37.8]}
    initialZoom={12}
    onClick={(e) => console.log('Clicked:', e.lngLat)}
  >
    <Source id="lots" data={geojsonData}>
      <FillLayer id="lots-fill" fillColor="#22c55e" fillOpacity={0.7} />
    </Source>
  </Map>
</MapContainer>

Components

Map Container

<script lang="ts">
  import { MapContainer, MapSkeleton, MapError, MapEmpty } from '@classic-homes/maps-svelte';

  let isLoading = $state(true);
  let error = $state<string | null>(null);
</script>

<MapContainer class="h-[500px]">
  {#if isLoading}
    <MapSkeleton />
  {:else if error}
    <MapError message={error} onRetry={() => reload()} />
  {:else}
    <Map>...</Map>
  {/if}
</MapContainer>

Core Map

<script lang="ts">
  import { Map, useMapContext } from '@classic-homes/maps-svelte';
  import { lightBasicStyle } from '@classic-homes/maps-core';

  function handleClick(e) {
    console.log('Clicked:', e.lngLat);
  }

  function handleMove(e) {
    console.log('Viewport:', e.center, e.zoom);
  }
</script>

<Map
  initialCenter={[-122.4, 37.8]}
  initialZoom={12}
  initialBearing={0}
  initialPitch={0}
  {bounds}
  fitBoundsOptions={{ padding: 50 }}
  style={lightBasicStyle}
  theme="light"
  interactive={true}
  scrollZoom={true}
  dragPan={true}
  onClick={handleClick}
  onHover={handleHover}
  onMove={handleMove}
  onLoad={handleLoad}
>
  <slot />
</Map>

Accessing Map Context

<script lang="ts">
  import { useMapContext, tryUseMapContext } from '@classic-homes/maps-svelte';

  // Throws if not within Map component
  const { map, loaded } = useMapContext();

  // Returns null if not within Map
  const context = tryUseMapContext();
</script>

Data Sources

<script lang="ts">
  import { Source } from '@classic-homes/maps-svelte';
</script>

<!-- GeoJSON data -->
<Source id="features" data={geojsonFeatureCollection}>
  <!-- Layers go here -->
</Source>

<!-- URL source -->
<Source id="remote" data="https://example.com/data.geojson">
  <!-- Layers go here -->
</Source>

<!-- With clustering -->
<Source id="points" data={pointData} cluster={true} clusterRadius={50} clusterMaxZoom={14}>
  <!-- Layers go here -->
</Source>

Layer Components

<script lang="ts">
  import {
    FillLayer,
    LineLayer,
    CircleLayer,
    SymbolLayer,
    FillExtrusionLayer,
    HeatmapLayer,
  } from '@classic-homes/maps-svelte';
</script>

<!-- Fill layer (polygons) -->
<FillLayer
  id="polygons"
  source="features"
  fillColor="#22c55e"
  fillOpacity={0.7}
  fillOutlineColor="#166534"
  onClick={handleClick}
  onHover={handleHover}
/>

<!-- Line layer -->
<LineLayer id="lines" source="features" lineColor="#3b82f6" lineWidth={2} lineCap="round" />

<!-- Circle layer (points) -->
<CircleLayer
  id="points"
  source="features"
  circleRadius={6}
  circleColor="#ef4444"
  circleStrokeWidth={2}
  circleStrokeColor="#ffffff"
/>

<!-- Symbol layer (labels/icons) -->
<SymbolLayer
  id="labels"
  source="features"
  textField={['get', 'name']}
  textSize={12}
  iconImage="marker"
/>

<!-- 3D extrusions -->
<FillExtrusionLayer
  id="buildings"
  source="features"
  fillExtrusionHeight={['get', 'height']}
  fillExtrusionColor="#94a3b8"
  fillExtrusionOpacity={0.9}
/>

<!-- Heatmap -->
<HeatmapLayer
  id="heat"
  source="features"
  heatmapWeight={1}
  heatmapIntensity={0.5}
  heatmapRadius={30}
/>

Markers and Popups

<script lang="ts">
  import {
    Marker,
    Popup,
    PopupCard,
    PopupHeader,
    PopupContent,
    PopupFooter,
  } from '@classic-homes/maps-svelte';
</script>

<!-- Custom marker with popup -->
<Marker
  lngLat={[-122.4, 37.8]}
  anchor="bottom"
  draggable={true}
  onClick={handleClick}
  onDragEnd={handleDrag}
>
  <div class="custom-marker">
    <MapPinIcon />
  </div>
</Marker>

<!-- Popup with card styling -->
<Popup lngLat={[-122.4, 37.8]} anchor="bottom" closeOnClick={false}>
  <PopupCard>
    <PopupHeader>
      <h3>Location Name</h3>
    </PopupHeader>
    <PopupContent>
      <p>Description goes here</p>
    </PopupContent>
    <PopupFooter>
      <button>View Details</button>
    </PopupFooter>
  </PopupCard>
</Popup>

Controls

<script lang="ts">
  import { Legend, LegendGroup, CoordinateDisplay } from '@classic-homes/maps-svelte';
</script>

<!-- Legend -->
<Legend
  position="bottom-left"
  title="Status"
  items={[
    { label: 'Available', color: '#22c55e' },
    { label: 'Pending', color: '#f59e0b' },
    { label: 'Sold', color: '#ef4444' },
  ]}
/>

<!-- Legend group with collapsible sections -->
<LegendGroup
  position="bottom-left"
  legends={[
    {
      title: 'Status',
      items: [{ label: 'Available', color: '#22c55e' }],
    },
    {
      title: 'Price',
      gradient: {
        stops: [
          { value: 0, color: '#eff6ff', label: '$0' },
          { value: 100, color: '#1e40af', label: '$1M+' },
        ],
      },
    },
  ]}
/>

<!-- Coordinate display -->
<CoordinateDisplay position="bottom-right" precision={4} />

Deck.gl Overlay

<script lang="ts">
  import { DeckOverlay } from '@classic-homes/maps-svelte';
  import { ScatterplotLayer } from '@deck.gl/layers';

  const layers = $derived([
    new ScatterplotLayer({
      id: 'scatter',
      data: points,
      getPosition: (d) => d.coordinates,
      getRadius: 100,
      getFillColor: [255, 0, 0],
    }),
  ]);
</script>

<Map>
  <DeckOverlay {layers} />
</Map>

Composables

useMapTheme

<script lang="ts">
  import { useMapTheme } from '@classic-homes/maps-svelte';

  const { theme, style, isDark, toggleTheme } = useMapTheme();
</script>

<Map style={style.current} theme={theme.current}>
  <slot />
</Map>
<button onclick={toggleTheme}>
  {isDark.current ? 'Light Mode' : 'Dark Mode'}
</button>

useReducedMotion

<script lang="ts">
  import { useReducedMotion } from '@classic-homes/maps-svelte';

  const prefersReducedMotion = useReducedMotion();

  function flyToLocation(lngLat) {
    map.flyTo({
      center: lngLat,
      duration: prefersReducedMotion.current ? 0 : 1000,
    });
  }
</script>

Cluster Utilities

<script lang="ts">
  import {
    isClusterFeature,
    expandCluster,
    getClusterBounds,
    expandClusterToBounds,
  } from '@classic-homes/maps-svelte';

  function handleClusterClick(e) {
    const feature = e.features[0];

    if (isClusterFeature(feature)) {
      // Option 1: Zoom to expand
      expandCluster(map, 'source-id', feature.id, (zoom) => {
        map.flyTo({ center: e.lngLat, zoom });
      });

      // Option 2: Fit to cluster bounds
      expandClusterToBounds(map, 'source-id', feature, (bounds) => {
        map.fitBounds(bounds, { padding: 50 });
      });
    }
  }
</script>

Reactive Updates

Svelte 5's fine-grained reactivity automatically handles prop updates:

<script lang="ts">
  let data = $state(initialData);
  let fillColor = $state('#22c55e');

  // Data changes automatically update the source
  function updateData(newData) {
    data = newData;
  }

  // Style changes automatically update the layer
  function updateColor(color) {
    fillColor = color;
  }
</script>

<Source id="lots" {data}>
  <FillLayer id="lots-fill" {fillColor} fillOpacity={0.7} />
</Source>

TypeScript

All components are fully typed:

import type {
  MapContextValue,
  SvelteFeatureClickEventParams,
  SvelteFeatureHoverEventParams,
} from '@classic-homes/maps-svelte';

import type {
  MapClickEventParams,
  MapHoverEventParams,
  FeatureClickEventParams,
  GeoJSONFeatureCollection,
  LngLat,
} from '@classic-homes/maps-core';

Peer Dependencies

  • svelte >= 5.0.0
  • maplibre-gl >= 4.0.0 || >= 5.0.0

License

MIT