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

@page-speed/maps

v0.2.4

Published

Performance-optimized MapLibre React components and style utilities for DashTrack.

Readme

@page-speed/maps

High-performance MapLibre primitives. An open source tool by OpenSite AI

PageSpeed Map React Component

npm version npm downloads License TypeScript Tree-Shakeable

Install

pnpm add @page-speed/maps maplibre-gl react-map-gl

Quick Start

import { MapLibre } from "@page-speed/maps";

export function Example() {
  return (
    <div style={{ width: "100%", height: 420 }}>
      <MapLibre
        stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
        mapStyle="osm-bright"
        viewState={{ latitude: 40.7128, longitude: -74.006, zoom: 12 }}
        markers={[
          {
            id: "nyc",
            latitude: 40.7128,
            longitude: -74.006,
            label: "New York"
          }
        ]}
      />
    </div>
  );
}

Why This Package

  • Explicit Stadia auth: no hard-coded keys
  • Auto-loads MapLibre CSS: no extra stylesheet import required
  • Keyless fallback map style: if no Stadia key is available, roads/landmarks still render via Carto Positron
  • Tree-shakable exports: import only what you need
  • Auto-centering hooks: compute optimal center and zoom for any set of coordinates
  • Drop-in API compatibility: works with the MapLibre component used in dt-cms

Table of Contents


Components

MapLibre / DTMapLibreMap

The main map component. Renders a MapLibre GL map with markers, controls, and full interactivity.

import { MapLibre } from "@page-speed/maps";

<MapLibre
  stadiaApiKey="your-api-key"
  mapStyle="osm-bright"
  viewState={{ latitude: 33.4484, longitude: -112.074, zoom: 10 }}
  markers={[
    { id: "phx", latitude: 33.4484, longitude: -112.074, label: "Phoenix" }
  ]}
  showNavigationControl
  showGeolocateControl
  onClick={(coord) => console.log("Clicked:", coord)}
  onMoveEnd={(center, zoom, bounds) => console.log("Moved:", center, zoom)}
/>

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | stadiaApiKey | string | required | Your Stadia Maps API key | | mapStyle | string | "osm-bright" | Built-in style name or custom style URL | | viewState | Partial<MapViewState> | - | Controlled view state (lat, lng, zoom) | | onViewStateChange | (state) => void | - | Callback when view state changes | | markers | Array<MapLibreMarker \| BasicMarkerInput> | [] | Array of markers to display | | center | { lat, lng } | - | Initial center (alternative to viewState) | | zoom | number | 14 | Initial zoom level | | styleUrl | string | - | Custom style URL (overrides mapStyle) | | onClick | (coord) => void | - | Map click handler | | onMoveEnd | (center, zoom, bounds) => void | - | Called when map stops moving | | onMarkerDrag | (markerId, coord) => void | - | Called when draggable marker moves | | showNavigationControl | boolean | true | Show zoom/rotation controls | | showGeolocateControl | boolean | false | Show user location button | | navigationControlPosition | MapControlPosition | "bottom-right" | Position of nav controls | | geolocateControlPosition | MapControlPosition | "top-left" | Position of geolocate button | | flyToOptions | MapLibreFlyToOptions | {} | Animation options for flyTo | | className | string | - | CSS class for wrapper div | | style | CSSProperties | - | Inline styles for wrapper | | children | ReactNode | - | Additional map layers/overlays | | mapLibreCssHref | string | jsDelivr CDN | Custom MapLibre CSS URL |


Hooks

useGeoCenter

Computes the geographic center of an array of coordinates using the Cartesian 3D averaging method. Handles antimeridian crossing and polar coordinates correctly.

import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
// or
import { useGeoCenter } from "@page-speed/maps";

const markers = [
  { lat: 33.4585, lng: -112.0715 },  // Downtown Phoenix
  { lat: 33.6510, lng: -111.9244 },  // Scottsdale
  { lat: 33.3062, lng: -111.8413 },  // Mesa
];

const center = useGeoCenter(markers);
// Result: { lat: 33.4719, lng: -111.9457 }

API

function useGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;
function computeGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;

interface GeoCoordinate {
  lat: number;
  lng: number;
}

interface GeoCenterResult {
  lat: number;
  lng: number;
}

Behavior

  • Empty array: Returns null
  • Single coordinate: Returns that coordinate
  • Multiple coordinates: Returns the geographic midpoint

useDefaultZoom

Computes the optimal zoom level to fit all coordinates within a given viewport, using Mercator projection math. Uses MapLibre's native 512px tile size.

import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";
// or
import { useDefaultZoom } from "@page-speed/maps";

const markers = [
  { lat: 33.4585, lng: -112.0715 },
  { lat: 33.6510, lng: -111.9244 },
];

const zoom = useDefaultZoom({
  coordinates: markers,
  mapWidth: 600,
  mapHeight: 400,
  padding: 50,
  maxZoom: 16,
  minZoom: 1,
});
// Result: ~10.5 (fits both markers with padding)

API

function useDefaultZoom(options: DefaultZoomOptions): number | null;
function computeDefaultZoom(options: DefaultZoomOptions): number | null;

interface DefaultZoomOptions {
  coordinates: GeoCoordinate[];
  mapWidth: number;
  mapHeight: number;
  padding?: number;   // default: 50
  maxZoom?: number;   // default: 18
  minZoom?: number;   // default: 1
}

Behavior

  • Empty array: Returns null
  • Single coordinate: Returns maxZoom
  • Multiple coordinates: Returns the highest zoom that fits all markers with padding
  • Invalid dimensions: Returns null or minZoom

Combined Usage: Auto-Centering Map

import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";

function AutoCenteringMap({ locations }) {
  const coordinates = locations.map(loc => ({
    lat: loc.latitude,
    lng: loc.longitude,
  }));

  const center = useGeoCenter(coordinates);
  const zoom = useDefaultZoom({
    coordinates,
    mapWidth: 800,
    mapHeight: 600,
    padding: 60,
  });

  if (!center) return <div>No locations to display</div>;

  return (
    <div style={{ width: 800, height: 600 }}>
      <MapLibre
        stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
        viewState={{
          latitude: center.lat,
          longitude: center.lng,
          zoom: zoom ?? 10,
        }}
        markers={locations.map((loc, i) => ({
          id: loc.id ?? i,
          latitude: loc.latitude,
          longitude: loc.longitude,
          label: loc.name,
        }))}
      />
    </div>
  );
}

Utilities

getMapLibreStyleUrl(style, stadiaApiKey)

Resolves a style name or URL to a fully-qualified MapLibre style URL with authentication.

import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";

const url = getMapLibreStyleUrl("osm-bright", "your-api-key");
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"

const fallback = getMapLibreStyleUrl("osm-bright", "");
// "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" (keyless fallback)

appendStadiaApiKey(styleUrl, stadiaApiKey)

Appends the Stadia API key to a style URL if it's a Stadia Maps URL.

import { appendStadiaApiKey } from "@page-speed/maps/utils/style-url";

const url = appendStadiaApiKey(
  "https://tiles.stadiamaps.com/styles/osm_bright.json",
  "your-api-key"
);
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"

generateGoogleMapLink(latitude, longitude, zoom?)

Generates a Google Maps URL for a location.

import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";

const url = generateGoogleMapLink(33.4484, -112.074, 15);
// "https://www.google.com/maps/@33.4484,-112.074,15z"

generateGoogleDirectionsLink(latitude, longitude)

Generates a Google Maps directions URL to a destination.

import { generateGoogleDirectionsLink } from "@page-speed/maps/utils/google-links";

const url = generateGoogleDirectionsLink(33.4484, -112.074);
// "https://www.google.com/maps/dir/?api=1&destination=33.4484,-112.074"

Types

All types are exported from @page-speed/maps/types or the main entry point:

import type {
  BasicMarkerInput,
  MapControlPosition,
  MapCoordinate,
  MapLibreFlyToOptions,
  MapLibreMarker,
  MapLibreProps,
  MapViewState,
  GeoCoordinate,
  GeoCenterResult,
  DefaultZoomOptions,
  MapLibreBuiltInStyle,
} from "@page-speed/maps";

Key Types

type MapViewState = {
  latitude: number;
  longitude: number;
  zoom: number;
};

type MapCoordinate = {
  latitude: number;
  longitude: number;
};

type GeoCoordinate = {
  lat: number;
  lng: number;
};

type BasicMarkerInput = {
  id?: string | number;
  latitude: number;
  longitude: number;
  color?: string;
  draggable?: boolean;
  label?: string;
  element?: (() => React.ReactNode) | React.ReactNode;
  onClick?: () => void;
};

type MapControlPosition =
  | "top-left"
  | "top-right"
  | "bottom-left"
  | "bottom-right";

Tree Shaking

This package supports granular tree-shaking. Import only what you need:

// Full bundle (all exports)
import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";

// Just the component
import { MapLibre } from "@page-speed/maps/core";

// Just hooks (tree-shakable)
import { useGeoCenter, useDefaultZoom } from "@page-speed/maps/hooks";

// Individual hooks (maximum tree-shaking)
import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";

// Just utilities
import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";
import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";

// Just types
import type { MapLibreProps } from "@page-speed/maps/types";

Map Styles

Built-in style presets (requires Stadia API key):

| Style Name | Description | |------------|-------------| | osm-bright | Clean, bright OpenStreetMap style (default) | | alidade-smooth | Modern, smooth cartography | | alidade-smooth-dark | Dark theme variant | | stadia-outdoors | Outdoor/terrain focused | | stamen-toner | High-contrast black & white | | stamen-terrain | Terrain with hillshading | | stamen-watercolor | Artistic watercolor style | | maplibre-default | Carto Positron (no API key required) |

<MapLibre mapStyle="stamen-terrain" stadiaApiKey="..." />

Or use a custom style URL:

<MapLibre styleUrl="https://your-tiles.com/style.json" stadiaApiKey="..." />

Advanced Usage

Custom Markers

Pass a custom React element for full control over marker rendering:

<MapLibre
  stadiaApiKey="..."
  markers={[
    {
      id: "custom",
      latitude: 33.4484,
      longitude: -112.074,
      element: (
        <div className="custom-marker">
          <img src="/pin.svg" alt="Location" />
          <span>Phoenix HQ</span>
        </div>
      ),
    },
  ]}
/>

Draggable Markers

<MapLibre
  stadiaApiKey="..."
  markers={[
    {
      id: "draggable",
      latitude: 33.4484,
      longitude: -112.074,
      draggable: true,
      label: "Drag me!",
    },
  ]}
  onMarkerDrag={(markerId, coord) => {
    console.log(`Marker ${markerId} moved to:`, coord);
  }}
/>

Controlled View State

function ControlledMap() {
  const [viewState, setViewState] = useState({
    latitude: 33.4484,
    longitude: -112.074,
    zoom: 12,
  });

  return (
    <>
      <MapLibre
        stadiaApiKey="..."
        viewState={viewState}
        onViewStateChange={setViewState}
      />
      <button onClick={() => setViewState(prev => ({ ...prev, zoom: prev.zoom + 1 }))}>
        Zoom In
      </button>
    </>
  );
}

Fly To Animation

<MapLibre
  stadiaApiKey="..."
  viewState={viewState}
  flyToOptions={{
    speed: 1.2,
    curve: 1.5,
    easing: (t) => t,
  }}
/>

Composing with UI Libraries

This package provides the core map primitives. For feature-rich components with clustering, info windows, and styled markers, see @opensite/ui which builds on top of @page-speed/maps:

// In @opensite/ui (consumer library)
import { useGeoCenter, useDefaultZoom, type GeoCoordinate } from "@page-speed/maps/hooks";

function GeoMap({ markers, clusters, defaultViewState }) {
  // Collect all coordinates
  const allCoordinates: GeoCoordinate[] = [
    ...markers.map(m => ({ lat: m.latitude, lng: m.longitude })),
    ...clusters.map(c => ({ lat: c.latitude, lng: c.longitude })),
  ];

  // Auto-compute center and zoom
  const geoCenter = useGeoCenter(allCoordinates);
  const defaultZoom = useDefaultZoom({
    coordinates: allCoordinates,
    mapWidth: 600,
    mapHeight: 520,
    padding: 60,
  });

  return (
    <MapLibre
      viewState={{
        latitude: defaultViewState?.latitude ?? geoCenter?.lat ?? 0,
        longitude: defaultViewState?.longitude ?? geoCenter?.lng ?? 0,
        zoom: defaultViewState?.zoom ?? defaultZoom ?? 10,
      }}
      // ... clustering, custom markers, info windows, etc.
    />
  );
}

API Reference

Exports Summary

| Export | Path | Description | |--------|------|-------------| | MapLibre | @page-speed/maps | Main map component | | DTMapLibreMap | @page-speed/maps | Alias for MapLibre | | useGeoCenter | @page-speed/maps/hooks/useGeoCenter | Geographic center hook | | computeGeoCenter | @page-speed/maps/hooks/useGeoCenter | Pure function version | | useDefaultZoom | @page-speed/maps/hooks/useDefaultZoom | Auto-zoom hook | | computeDefaultZoom | @page-speed/maps/hooks/useDefaultZoom | Pure function version | | getMapLibreStyleUrl | @page-speed/maps/utils/style-url | Style URL resolver | | appendStadiaApiKey | @page-speed/maps/utils/style-url | API key appender | | generateGoogleMapLink | @page-speed/maps/utils/google-links | Google Maps link | | generateGoogleDirectionsLink | @page-speed/maps/utils/google-links | Google Directions link |


License

BSD-3-Clause. See LICENSE for details.


New: Feature-Rich Components

GeoMap

Full-featured map component with markers, clusters, rich media panels, and automatic view calculation.

import { GeoMap, createMapMarkerElement } from "@page-speed/maps";
import type { GeoMapMarker } from "@page-speed/maps";

const markers: GeoMapMarker[] = [
  {
    id: 'office',
    latitude: 40.7128,
    longitude: -74.0060,
    title: 'New York Office',
    summary: 'Our headquarters in downtown Manhattan',
    locationLine: '123 Broadway, New York, NY 10001',
    hoursLine: 'Mon-Fri: 9:00 AM - 6:00 PM',
    mediaItems: [
      { id: '1', src: '/office.jpg', alt: 'Office' },
    ],
    markerElement: createMapMarkerElement({ size: 'lg' }),
    actions: [
      {
        label: 'Get Directions',
        href: 'https://maps.app.goo.gl/example',
      },
    ],
  },
];

<GeoMap
  markers={markers}
  stadiaApiKey="your-key"
  panelPosition="bottom-left"
  showNavigationControl
/>

Key Features:

  • ✅ Auto-calculated center and zoom from markers
  • ✅ Rich media carousels (images/videos)
  • ✅ Interactive marker panels
  • ✅ Clustering support
  • ✅ Custom marker elements
  • ✅ Action buttons and links

→ Full GeoMap Documentation

MapMarker

Beautiful concentric circle markers with hover and selection states.

import { MapMarker, NeutralMapMarker, createMapMarkerElement } from "@page-speed/maps";

// Direct usage
<MapMarker
  size="lg"
  isSelected
  dotColor="#1E40AF"
  innerRingColor="#3B82F6"
  middleRingColor="#93C5FD"
  outerRingColor="#DBEAFE"
/>

// With GeoMap
const markers = [{
  id: 'loc-1',
  latitude: 40.7128,
  longitude: -74.0060,
  markerElement: createMapMarkerElement({ size: 'lg' }),
}];

Sizes: sm | md | lg
Pre-configured: NeutralMapMarker for neutral gray design

→ MapMarker Examples


Migration from @opensite/ui

If you're migrating map components from @opensite/ui to @page-speed/maps, see our comprehensive migration guide:

→ Migration Guide

Key Changes:

  • ✅ Fixed zoom/centering bugs
  • ✅ New MapMarker components
  • ✅ Better tree-shakability
  • ✅ Optional peer dependencies for icons/images

Documentation


Related Packages


Made with ❤️ for the DashTrack Platform