@sckyzo/cosmos-react 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/useControllableState.ts","../../src/settings/appearance.ts","../../src/settings/useCosmosTheme.ts","../../src/map/MapPin.tsx","../../src/map/mapGeo.ts","../../src/map/MapView.tsx","../../src/map/MapMarkerList.tsx"],"names":["useState","useRef","useInsertionEffect","useCallback","useEffect","useSyncExternalStore","jsx","PinIcon","clsx","jsxs","Fragment","X","createPortal","Plus","Minus","MapPin"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';\n\n/**\n * The value to move to, or a function of the current one, as with `setState`. `S` is the\n * state, which may be empty (`undefined`) before the first set; `T` is what a set moves to.\n */\nexport type ControllableUpdate<T, S = T> = T | ((current: S) => T);\n\nexport type ControllableStateOptions<T, A extends unknown[] = [], D extends T | undefined = T> = {\n /**\n * The value the host controls. Only `undefined` means uncontrolled: `null`, `false`, `0`\n * and `''` are controlled values.\n */\n value: T | undefined;\n /**\n * Starting value while uncontrolled, read on the first render only. May be `undefined` for\n * a state that starts empty (no option picked yet): the state then reads `T | undefined`,\n * while the setter and `onChange` keep to `T`.\n */\n defaultValue: D | (() => D);\n /**\n * Called on every set, controlled or not, with the value to move to and the setter's extra\n * arguments (a field passes its native event).\n */\n onChange?: (value: T, ...args: A) => void;\n /**\n * `Component.prop`, for the console warning when the host switches between controlled and\n * uncontrolled.\n */\n name: string;\n};\n\n/**\n * State a host may control. Returns the current value, a setter and whether the value is\n * controlled.\n *\n * The setter keeps its identity across renders and reads the latest `onChange` and value, so\n * effects and memoized handlers can depend on it without a stale closure. It updates the\n * internal state only while uncontrolled, and always calls `onChange`: a controlled\n * component shows the new value once the host passes it back.\n *\n * Internal to the package, not exported. `T` must not be a function type: a function passed\n * to the setter is an updater, as with `setState`.\n */\nexport function useControllableState<T, A extends unknown[] = [], D extends T | undefined = T>({\n value,\n defaultValue,\n onChange,\n name,\n}: ControllableStateOptions<T, A, D>): [\n value: T | D,\n setValue: (next: ControllableUpdate<T, T | D>, ...args: A) => void,\n isControlled: boolean,\n] {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState<T | D>(defaultValue);\n const current = isControlled ? value : internal;\n\n // What the setter reads. Refreshed after each render, before any effect or event handler\n // can call the setter.\n const latest = useRef({ current, isControlled, onChange });\n useInsertionEffect(() => {\n latest.current = { current, isControlled, onChange };\n });\n\n const setValue = useCallback((next: ControllableUpdate<T, T | D>, ...args: A) => {\n const state = latest.current;\n const resolved =\n typeof next === 'function' ? (next as (current: T | D) => T)(state.current) : next;\n if (!state.isControlled) {\n // A second set in the same event builds on this one, as with setState.\n state.current = resolved;\n setInternal(resolved);\n }\n state.onChange?.(resolved, ...args);\n }, []);\n\n useModeSwitchWarning(isControlled, name);\n\n return [current, setValue, isControlled];\n}\n\n// Typed here: the package depends on no Node types.\ndeclare const process: { env: { NODE_ENV?: string } };\n\n/**\n * Whether the host app is a production build. Bundlers (Vite, webpack, Next) replace\n * `process.env.NODE_ENV` at build time, as React's own warnings rely on; without a bundler\n * `process` doesn't exist, and the ReferenceError means \"not a production build\".\n */\nconst isProductionBuild = (() => {\n try {\n return process.env.NODE_ENV === 'production';\n } catch {\n return false;\n }\n})();\n\n/**\n * Warns once per component when its value goes from controlled to uncontrolled or back,\n * usually a host passing `undefined` for an empty value. Development builds only, like\n * React's warnings.\n */\nfunction useModeSwitchWarning(isControlled: boolean, name: string) {\n const initial = useRef(isControlled);\n const warned = useRef(false);\n useEffect(() => {\n if (isProductionBuild || warned.current || initial.current === isControlled) return;\n warned.current = true;\n const from = initial.current ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `Cosmos: ${name} switched from ${from} to ${to}. Keep it one or the other for the ` +\n 'life of the component: only `undefined` means uncontrolled.'\n );\n }, [isControlled, name]);\n}\n","import type { AccentColor } from './AccentPicker.tsx';\nimport type { PaletteMeta } from './PaletteCard.tsx';\n\n/** A Cosmos theme: one class on `<html>`, none for `light`. */\nexport type CosmosTheme = 'light' | 'sand' | 'dark' | 'deep-blue';\n\n/**\n * What a user can choose as a theme: a Cosmos theme, or `system` to follow\n * `prefers-color-scheme`. `system` is not a theme of its own (it has no CSS):\n * `applyAppearance` resolves it to the light or the dark theme.\n */\nexport type CosmosThemeChoice = CosmosTheme | 'system';\n\n/** The themes `system` resolves to. */\nexport type CosmosSystemThemes = { light: CosmosTheme; dark: CosmosTheme };\n\n/** A Cosmos accent: the `data-accent` attribute on `<html>`, none for `blue`. */\nexport type CosmosAccent =\n | 'blue'\n | 'indigo'\n | 'violet'\n | 'purple'\n | 'rose'\n | 'teal'\n | 'cyan'\n | 'graphite'\n | 'red'\n | 'orange'\n | 'amber'\n | 'green';\n\n/** A Cosmos font: the `data-font` attribute on `<html>`, none for `outfit`. */\nexport type CosmosFont = 'outfit' | 'inter' | 'plex' | 'montserrat' | 'open-sans' | 'system';\n\n/** A Cosmos text size: the `data-text-size` attribute on `<html>`, none for `md`. */\nexport type CosmosTextSize = 'sm' | 'md' | 'lg' | 'xl';\n\n/** A Cosmos density: the `data-density` attribute on `<html>`, none for `default`. */\nexport type CosmosDensity = 'compact' | 'default' | 'comfortable';\n\n/**\n * A Cosmos corner radius: the `data-radius` attribute on `<html>`, none for `default`.\n * `none` squares every corner to 2px, `sm` halves them, `lg` takes them one and a half times.\n */\nexport type CosmosRadius = 'none' | 'sm' | 'default' | 'lg';\n\n/** The reading direction of the page. */\nexport type CosmosDirection = 'ltr' | 'rtl';\n\nexport type CosmosThemeMeta = PaletteMeta & { id: CosmosTheme; dark: boolean };\n/** The `system` entry for `<PalettePicker>`: see `COSMOS_SYSTEM_THEME`. */\nexport type CosmosSystemThemeMeta = PaletteMeta & { id: 'system' };\nexport type CosmosAccentMeta = AccentColor & { id: CosmosAccent };\n/** `family` is the CSS font stack the theme sets for this font. */\nexport type CosmosFontMeta = { id: CosmosFont; label: string; family: string };\n/** `scale` is the factor the theme applies to every text size. */\nexport type CosmosTextSizeMeta = { id: CosmosTextSize; label: string; scale: number };\nexport type CosmosDensityMeta = { id: CosmosDensity; label: string };\nexport type CosmosRadiusMeta = { id: CosmosRadius; label: string };\n\n/**\n * The page's appearance. `font`, `textSize`, `density`, `radius` and `direction`\n * are optional: leave them out and `applyAppearance` doesn't touch their attributes.\n */\nexport type CosmosAppearance = {\n /** `system` follows `prefers-color-scheme`: see `systemThemes`. */\n theme: CosmosThemeChoice;\n /** The themes `system` resolves to. Default `{ light: 'light', dark: 'dark' }`. */\n systemThemes?: CosmosSystemThemes;\n accent: CosmosAccent;\n font?: CosmosFont;\n textSize?: CosmosTextSize;\n density?: CosmosDensity;\n radius?: CosmosRadius;\n /** The reading direction: the `dir` attribute on `<html>`, none for `ltr`. */\n direction?: CosmosDirection;\n};\n\n/** The four themes shipped by `@sckyzo/cosmos-theme`, ready for `<PalettePicker>`. */\nexport const COSMOS_THEMES: CosmosThemeMeta[] = [\n {\n id: 'light',\n label: 'Light',\n dark: false,\n preview: { bg: '#f9fafb', surface: '#ffffff', border: '#e5e7eb', text: '#667085' },\n },\n {\n id: 'sand',\n label: 'Sand',\n dark: false,\n preview: { bg: '#f9f5eb', surface: '#fffcf7', border: '#e7dfd0', text: '#746656' },\n },\n {\n id: 'dark',\n label: 'Dark',\n dark: true,\n preview: { bg: '#0a0a0a', surface: '#141414', border: '#3d3d3d', text: '#a3a3a3' },\n },\n {\n id: 'deep-blue',\n label: 'Deep Blue',\n dark: true,\n preview: { bg: '#0c111d', surface: '#101828', border: '#344054', text: '#98a2b3' },\n },\n];\n\n/**\n * The opt-in `system` choice for `<PalettePicker>` / `<AppearanceSettings>`,\n * which no list offers by default: `themes={[COSMOS_SYSTEM_THEME, ...COSMOS_THEMES]}`.\n * Its mockup is a neutral mid-tone, since it shows whichever theme the device uses.\n */\nexport const COSMOS_SYSTEM_THEME: CosmosSystemThemeMeta = {\n id: 'system',\n label: 'System',\n preview: { bg: '#6b7280', surface: '#9ca3af', border: '#4b5563', text: '#111827' },\n};\n\n/**\n * The twelve accents, ready for `<AccentPicker>`. `hex` is the accent's\n * `brand-500`. Red, orange, amber and green look like the status colors, so\n * they come last.\n */\nexport const COSMOS_ACCENTS: CosmosAccentMeta[] = [\n { id: 'blue', label: 'Blue', hex: '#465fff' },\n { id: 'indigo', label: 'Indigo', hex: '#5963e9' },\n { id: 'violet', label: 'Violet', hex: '#8a49f5' },\n { id: 'purple', label: 'Purple', hex: '#b234d4' },\n { id: 'rose', label: 'Rose', hex: '#d81e70' },\n { id: 'teal', label: 'Teal', hex: '#038378' },\n { id: 'cyan', label: 'Cyan', hex: '#017f9d' },\n { id: 'graphite', label: 'Graphite', hex: '#707580' },\n { id: 'red', label: 'Red', hex: '#de1f2d' },\n { id: 'orange', label: 'Orange', hex: '#c04f01' },\n { id: 'amber', label: 'Amber', hex: '#a46800' },\n { id: 'green', label: 'Green', hex: '#02863c' },\n];\n\n/**\n * The fonts of `@sckyzo/cosmos-theme`. Outfit loads from\n * `@sckyzo/cosmos-theme/fonts`, each other one from `fonts/<id>`; `system`\n * loads nothing. Only offer the fonts whose CSS the app imports.\n */\nexport const COSMOS_FONTS: CosmosFontMeta[] = [\n { id: 'outfit', label: 'Outfit', family: \"'Outfit', system-ui, sans-serif\" },\n { id: 'inter', label: 'Inter', family: \"'Inter Variable', system-ui, sans-serif\" },\n { id: 'plex', label: 'IBM Plex Sans', family: \"'IBM Plex Sans Variable', system-ui, sans-serif\" },\n { id: 'montserrat', label: 'Montserrat', family: \"'Montserrat Variable', system-ui, sans-serif\" },\n { id: 'open-sans', label: 'Open Sans', family: \"'Open Sans Variable', system-ui, sans-serif\" },\n {\n id: 'system',\n label: 'System',\n family: \"system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif\",\n },\n];\n\n/** The four text sizes of `@sckyzo/cosmos-theme`, smallest first. */\nexport const COSMOS_TEXT_SIZES: CosmosTextSizeMeta[] = [\n { id: 'sm', label: 'Small', scale: 0.9375 },\n { id: 'md', label: 'Default', scale: 1 },\n { id: 'lg', label: 'Large', scale: 1.125 },\n { id: 'xl', label: 'Extra large', scale: 1.25 },\n];\n\n/**\n * The three densities of `@sckyzo/cosmos-theme`, tightest first. They move the\n * height of controls and the vertical padding of rows, nothing else.\n */\nexport const COSMOS_DENSITIES: CosmosDensityMeta[] = [\n { id: 'compact', label: 'Compact' },\n { id: 'default', label: 'Default' },\n { id: 'comfortable', label: 'Comfortable' },\n];\n\n/**\n * The four corner radii of `@sckyzo/cosmos-theme`, sharpest first. They rescale\n * Tailwind's `--radius-*` tokens (`rounded-sm` to `rounded-4xl`); `rounded-full`\n * never moves.\n */\nexport const COSMOS_RADII: CosmosRadiusMeta[] = [\n { id: 'none', label: 'None' },\n { id: 'sm', label: 'Small' },\n { id: 'default', label: 'Default' },\n { id: 'lg', label: 'Large' },\n];\n\nconst THEME_CLASSES = COSMOS_THEMES.map((t) => t.id).filter((id) => id !== 'light');\n\n/** `true` for the themes that enable Tailwind's `dark:` variant. */\nexport const isDarkTheme = (theme: CosmosTheme): boolean =>\n COSMOS_THEMES.some((t) => t.id === theme && t.dark);\n\nconst DARK_QUERY = '(prefers-color-scheme: dark)';\n\n/** Whether the device asks for a dark color scheme. `false` during SSR or without `matchMedia`. */\nexport const systemPrefersDark = (): boolean =>\n typeof window !== 'undefined' && !!window.matchMedia?.(DARK_QUERY).matches;\n\n/**\n * The theme to apply for a choice: the theme itself, or for `system` the dark\n * or the light one of `systemThemes` according to `prefersDark`.\n */\nexport const resolveTheme = (\n choice: CosmosThemeChoice,\n prefersDark: boolean,\n systemThemes: CosmosSystemThemes = { light: 'light', dark: 'dark' }\n): CosmosTheme =>\n choice === 'system' ? (prefersDark ? systemThemes.dark : systemThemes.light) : choice;\n\n/**\n * Calls `onChange(prefersDark)` when the device switches its color scheme, and\n * returns the function that stops it. Use it while the theme is `system`, to\n * apply the appearance again. No-op during SSR or without `matchMedia`.\n */\nexport const watchSystemTheme = (onChange: (prefersDark: boolean) => void): (() => void) => {\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\n const query = window.matchMedia(DARK_QUERY);\n const listener = (event: MediaQueryListEvent) => onChange(event.matches);\n query.addEventListener('change', listener);\n return () => query.removeEventListener('change', listener);\n};\n\n/**\n * Apply an appearance to `root` (default `<html>`): swaps the theme class and\n * sets `data-accent`, `data-font`, `data-text-size`, `data-density` and\n * `data-radius`, and `dir=\"rtl\"` for a `direction` of `rtl`, removing each one\n * for its default (blue, Outfit, `md`, `default`, `default`, `ltr`). A `system`\n * theme is resolved with `prefers-color-scheme` when applied: call this again\n * on `watchSystemTheme`. A `font`, `textSize`, `density`, `radius` or `direction`\n * left out keeps its attribute as it is. No-op during SSR.\n */\nexport const applyAppearance = (\n {\n theme: choice,\n systemThemes,\n accent,\n font,\n textSize,\n density,\n radius,\n direction,\n }: CosmosAppearance,\n root?: HTMLElement\n) => {\n const el = root ?? (typeof document === 'undefined' ? undefined : document.documentElement);\n if (!el) return;\n const theme = resolveTheme(choice, systemPrefersDark(), systemThemes);\n el.classList.remove(...THEME_CLASSES);\n if (theme !== 'light') el.classList.add(theme);\n if (accent === 'blue') delete el.dataset.accent;\n else el.dataset.accent = accent;\n if (font === 'outfit') delete el.dataset.font;\n else if (font) el.dataset.font = font;\n if (textSize === 'md') delete el.dataset.textSize;\n else if (textSize) el.dataset.textSize = textSize;\n if (density === 'default') delete el.dataset.density;\n else if (density) el.dataset.density = density;\n if (radius === 'default') delete el.dataset.radius;\n else if (radius) el.dataset.radius = radius;\n if (direction === 'ltr') el.removeAttribute('dir');\n else if (direction) el.setAttribute('dir', direction);\n};\n","import { useSyncExternalStore } from 'react';\nimport { COSMOS_THEMES, type CosmosTheme, isDarkTheme } from './appearance';\n\n// The theme is a class on <html> (none = light), so it is read from the DOM\n// rather than a context: an app may switch it with `applyAppearance` or by hand.\nconst readTheme = (): CosmosTheme => {\n const { classList } = document.documentElement;\n return COSMOS_THEMES.find((t) => t.id !== 'light' && classList.contains(t.id))?.id ?? 'light';\n};\n\nconst subscribe = (onChange: () => void) => {\n const observer = new MutationObserver(onChange);\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n return () => observer.disconnect();\n};\n\nconst getSnapshot = (): 'light' | 'dark' => (isDarkTheme(readTheme()) ? 'dark' : 'light');\nconst getServerSnapshot = (): 'light' | 'dark' => 'light';\n\n/** `'dark'` under the dark Cosmos themes (`.dark`, `.deep-blue`), `'light'` otherwise. */\nexport const useCosmosTheme = (): 'light' | 'dark' =>\n useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n","import { clsx } from 'clsx';\nimport { MapPin as PinIcon, X } from 'lucide-react';\nimport type { Intent } from '../ui/intent';\nimport type { MapMarker } from './types';\n\n// A pin is HTML, not a map layer: its colour is a class, so a theme or accent switch repaints it\n// with no value read in JavaScript. Tailwind scans for whole class names.\nconst PIN_COLOR: Record<Intent, string> = {\n neutral: 'bg-gray-600 text-white',\n brand: 'bg-brand-500 text-white',\n info: 'bg-blue-600 text-white',\n success: 'bg-green-500 text-gray-900',\n warning: 'bg-amber-500 text-gray-900',\n danger: 'bg-red-600 text-white',\n};\n\nconst PIN_BASE =\n 'relative flex items-center justify-center rounded-full border-2 border-white text-xs font-semibold shadow-md transition-transform';\n\ntype PinProps = {\n marker: MapMarker;\n selected: boolean;\n /** `false` on a static map: the pin is a picture, with no button and no popup. */\n interactive: boolean;\n onSelect: (id: string) => void;\n onClose: () => void;\n};\n\n/** One marker, drawn inside the element MapLibre positions on the map. */\nexport const MapPin = ({ marker, selected, interactive, onSelect, onClose }: PinProps) => {\n const { id, label, description, intent = 'brand', icon } = marker;\n // A text icon (a number) is visible, so the name carries it too: axe checks that the visible text\n // is part of the name.\n const name = typeof icon === 'string' || typeof icon === 'number' ? `${label} ${icon}` : label;\n const face =\n icon != null ? (\n <span aria-hidden=\"true\">{icon}</span>\n ) : (\n <PinIcon className=\"size-4\" aria-hidden=\"true\" />\n );\n const className = clsx(\n PIN_BASE,\n 'size-8',\n PIN_COLOR[intent],\n selected ? 'ring-brand-500/40 scale-110 ring-4' : 'ring-0'\n );\n\n if (!interactive) {\n return (\n <span className={className} aria-hidden=\"true\">\n {face}\n </span>\n );\n }\n\n return (\n <>\n {/* Tab stops are the map and the list: a pin is reachable by pointer, and by the list. */}\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={name}\n aria-expanded={selected}\n className={clsx(className, 'focus-visible:focus-indicator cursor-pointer')}\n onClick={() => (selected ? onClose() : onSelect(id))}\n >\n {face}\n </button>\n {selected && (\n <div\n role=\"dialog\"\n aria-label={label}\n className=\"inset-s-1/2 absolute bottom-full mb-3 w-max max-w-60 -translate-x-1/2 rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-panel)] p-3 pe-9 text-start text-sm text-[var(--color-text-base)] shadow-lg rtl:translate-x-1/2\"\n >\n <p className=\"font-medium\">\n <bdi>{label}</bdi>\n </p>\n {description != null && (\n <div className=\"mt-0.5 text-xs text-[var(--color-text-secondary)]\">{description}</div>\n )}\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className=\"focus-visible:focus-indicator inset-e-1.5 absolute top-1.5 flex size-6 cursor-pointer items-center justify-center rounded-md text-[var(--color-text-secondary)] hover:bg-gray-100 dark:hover:bg-white/10\"\n >\n <X className=\"size-4\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n </>\n );\n};\n\ntype ClusterPinProps = { count: number; interactive: boolean; onOpen: () => void };\n\n/** A group of markers too close to tell apart at this zoom: a counted circle. */\nexport const ClusterPin = ({ count, interactive, onOpen }: ClusterPinProps) => {\n const size = count < 10 ? 'size-9' : count < 100 ? 'size-11' : 'size-14';\n const className = clsx(\n 'bg-brand-600 ring-brand-500/30 flex items-center justify-center rounded-full border-2 border-white text-sm font-semibold text-white shadow-md ring-4',\n size\n );\n if (!interactive) {\n return (\n <span className={className} aria-hidden=\"true\">\n {count}\n </span>\n );\n }\n return (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={`${count} places, zoom in`}\n className={clsx(className, 'focus-visible:focus-indicator cursor-pointer')}\n onClick={onOpen}\n >\n {count}\n </button>\n );\n};\n","import type { MapMarker } from './types';\n\nexport type Bounds = [[west: number, south: number], [east: number, north: number]];\n\n/** A position MapLibre can place: finite, longitude in -180..180, latitude in -90..90. */\nexport const isDrawable = ({ position: [lng, lat] }: MapMarker): boolean =>\n Number.isFinite(lng) && Number.isFinite(lat) && Math.abs(lng) <= 180 && Math.abs(lat) <= 90;\n\n/** The markers as a GeoJSON source: one point each, carrying its `id`. */\nexport const markersToGeoJSON = (markers: readonly MapMarker[]) => ({\n type: 'FeatureCollection' as const,\n features: markers.filter(isDrawable).map(({ id, position }) => ({\n type: 'Feature' as const,\n properties: { id },\n geometry: { type: 'Point' as const, coordinates: [position[0], position[1]] },\n })),\n});\n\n/** The smallest box around the drawable markers, or `null` when there is none. */\nexport const markerBounds = (markers: readonly MapMarker[]): Bounds | null => {\n const drawable = markers.filter(isDrawable);\n if (drawable.length === 0) return null;\n let west = Infinity;\n let south = Infinity;\n let east = -Infinity;\n let north = -Infinity;\n for (const {\n position: [lng, lat],\n } of drawable) {\n west = Math.min(west, lng);\n east = Math.max(east, lng);\n south = Math.min(south, lat);\n north = Math.max(north, lat);\n }\n return [\n [west, south],\n [east, north],\n ];\n};\n","import { useEffect, useInsertionEffect, useRef, useState, type Ref } from 'react';\nimport { createPortal } from 'react-dom';\nimport { clsx } from 'clsx';\nimport { Minus, Plus } from 'lucide-react';\nimport type {\n GeoJSONSource,\n Map as MapLibreMap,\n Marker as MapLibreMarker,\n StyleSpecification,\n} from 'maplibre-gl';\nimport { useControllableState } from '../hooks/useControllableState';\nimport { useCosmosTheme } from '../settings/useCosmosTheme';\nimport { ClusterPin, MapPin } from './MapPin';\nimport { isDrawable, markerBounds, markersToGeoJSON } from './mapGeo';\nimport type { MapMarker } from './types';\n\ntype MapLibre = typeof import('maplibre-gl');\n\nexport type MapViewState = {\n /** `[longitude, latitude]` of the middle of the map. */\n center: [lng: number, lat: number];\n zoom: number;\n};\n\nexport type MapViewProps = {\n /** The places to show. Define it once (state, memo or a constant): a new array redraws the pins. */\n markers: readonly MapMarker[];\n /**\n * The light map style: a MapLibre style URL or object. Cosmos ships none, since a tile server has\n * its own terms and sees every visitor. Pass a constant, not a new object on each render.\n */\n style: string | StyleSpecification;\n /** The style under the dark themes (`.dark`, `.deep-blue`). Without it the light one is kept. */\n darkStyle?: string | StyleSpecification;\n /**\n * `interactive` pans and zooms, takes the focus and shows the zoom buttons. `static` is a fixed\n * view for a card or a detail page: no gesture, no focus stop, no popup, and the pins are\n * pictures, so give the places to assistive technology with a `MapMarkerList`.\n */\n variant?: 'interactive' | 'static';\n /** Names the map for assistive technology. Default `Map`. */\n ariaLabel?: string;\n /** Height of the map, in pixels or any CSS length. Default 360. */\n height?: number | string;\n /** Frame the markers on the first draw, and again when the set of markers changes. Default `true`. */\n fit?: boolean;\n /** The view when `fit` is off or there is no marker. Not controlled: the map owns it afterwards. */\n initialCenter?: [lng: number, lat: number];\n initialZoom?: number;\n /** Group the markers that are close at the current zoom into a counted circle. Default `true`. */\n cluster?: boolean;\n /** The marker whose popup is open, `null` for none. */\n selectedId?: string | null;\n defaultSelectedId?: string | null;\n onSelectedIdChange?: (id: string | null) => void;\n /** The view after each pan or zoom, however it was triggered (pointer, keyboard, buttons). */\n onViewChange?: (view: MapViewState) => void;\n /** Shown in place of the map when the browser cannot draw it (no WebGL). */\n unavailableMessage?: string;\n className?: string;\n ref?: Ref<HTMLDivElement>;\n};\n\nconst SOURCE = 'cosmos-markers';\nconst CLUSTER_RADIUS = 48;\nconst CLUSTER_MAX_ZOOM = 14;\nconst FIT = { padding: 56, maxZoom: CLUSTER_MAX_ZOOM } as const;\n\nconst missingMapLibre = (cause: unknown) =>\n new Error(\n '@sckyzo/cosmos-react/map needs the optional peer dependency \"maplibre-gl\". Install it with `pnpm add maplibre-gl` (or `npm install maplibre-gl`).',\n { cause }\n );\n\nlet pending: Promise<MapLibre> | undefined;\n\n/** MapLibre, imported on the first map: it touches `window` on load, so never at module level. */\nconst loadMapLibre = () =>\n (pending ??= import('maplibre-gl').then(\n // A CommonJS build of the library comes back as `default`.\n (mod) => (mod as unknown as { default?: MapLibre }).default ?? mod,\n (error: unknown) => {\n pending = undefined;\n throw missingMapLibre(error);\n }\n ));\n\n/** MapLibre owns these elements, so React cannot style them: the open pin goes above the others. */\nconst raise = (el: HTMLElement, on: boolean) => {\n el.style.zIndex = on ? '2' : '';\n};\n\n/** What is on screen: a marker, or a group of them. `el` is the element MapLibre positions. */\ntype Visible = {\n key: string;\n el: HTMLElement;\n position: [lng: number, lat: number];\n marker: string | null;\n cluster: { id: number; count: number } | null;\n};\n\n/**\n * An interactive map of places, drawn with MapLibre GL JS (the optional peer `maplibre-gl`): pins\n * in Cosmos colours with an icon or a number, a popup per pin, clusters, zoom buttons and a fit to\n * the markers. Load MapLibre's stylesheet once in the app: `import 'maplibre-gl/dist/maplibre-gl.css'`.\n *\n * The pins, popups and buttons are React and HTML, so a title or description with markup shows as\n * text and the theme repaints them on its own. Only the base map (`style` / `darkStyle`) comes from\n * the host.\n */\nexport const MapView = ({\n markers,\n style,\n darkStyle,\n variant = 'interactive',\n ariaLabel = 'Map',\n height = 360,\n fit = true,\n initialCenter,\n initialZoom,\n cluster = true,\n selectedId: selectedIdProp,\n defaultSelectedId = null,\n onSelectedIdChange,\n onViewChange,\n unavailableMessage = 'The map could not be displayed.',\n className,\n ref,\n}: MapViewProps) => {\n const interactive = variant === 'interactive';\n const theme = useCosmosTheme();\n const wantedStyle = theme === 'dark' && darkStyle ? darkStyle : style;\n const [selectedId, setSelectedId] = useControllableState<string | null>({\n value: selectedIdProp,\n defaultValue: defaultSelectedId,\n onChange: onSelectedIdChange,\n name: 'MapView.selectedId',\n });\n\n const containerRef = useRef<HTMLDivElement>(null);\n const mapRef = useRef<MapLibreMap | null>(null);\n const appliedStyle = useRef(wantedStyle);\n const ariaLabelRef = useRef(ariaLabel);\n const reveal = useRef<(id: string) => void>(undefined);\n const refit = useRef<() => void>(undefined);\n const [visible, setVisible] = useState<Visible[]>([]);\n const [failure, setFailure] = useState<unknown>();\n const [unavailable, setUnavailable] = useState(false);\n\n // What the long-lived map callbacks read: the latest props, never a stale closure.\n const latest = useRef({ markers, fit, initialCenter, initialZoom, onViewChange, selectedId });\n useInsertionEffect(() => {\n latest.current = { markers, fit, initialCenter, initialZoom, onViewChange, selectedId };\n });\n\n // The map lives as long as its variant and clustering do; everything else is pushed into it.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n let disposed = false;\n let map: MapLibreMap | undefined;\n const pins = new Map<string, { marker: MapLibreMarker; el: HTMLElement }>();\n\n (async () => {\n let ml: MapLibre;\n try {\n ml = await loadMapLibre();\n } catch (error) {\n if (!disposed) setFailure(error);\n return;\n }\n if (disposed) return;\n\n const {\n markers: initial,\n fit: fitFirst,\n initialCenter: center,\n initialZoom: zoom,\n } = latest.current;\n const bounds = fitFirst ? markerBounds(initial) : null;\n try {\n map = new ml.Map({\n container,\n style: appliedStyle.current,\n interactive,\n attributionControl: { compact: false },\n ...(bounds ? { bounds, fitBoundsOptions: FIT } : { center, zoom }),\n });\n } catch {\n // No WebGL: the list next to the map still gives every place.\n if (!disposed) setUnavailable(true);\n return;\n }\n const current = map;\n mapRef.current = current;\n\n // The canvas is the map's focus stop: it carries the name, and a static map has none.\n const canvas = current.getCanvas();\n canvas.setAttribute('aria-label', ariaLabelRef.current);\n if (!interactive) {\n canvas.removeAttribute('tabindex');\n canvas.setAttribute('role', 'img');\n }\n\n const addSource = () => {\n if (current.getSource(SOURCE)) return;\n current.addSource(SOURCE, {\n type: 'geojson',\n data: markersToGeoJSON(latest.current.markers),\n cluster,\n clusterRadius: CLUSTER_RADIUS,\n clusterMaxZoom: CLUSTER_MAX_ZOOM,\n });\n // A source nothing draws is never tiled, and the pins are read from its tiles.\n current.addLayer({\n id: SOURCE,\n type: 'circle',\n source: SOURCE,\n paint: { 'circle-radius': 0, 'circle-opacity': 0 },\n });\n };\n\n // Pins are HTML elements MapLibre moves with the map; React fills them (portals below).\n const sync = () => {\n if (!current.getSource(SOURCE)) return;\n const next = new Map<string, Omit<Visible, 'el'>>();\n for (const feature of current.querySourceFeatures(SOURCE)) {\n const { cluster_id: clusterId, point_count: count, id } = feature.properties;\n const [lng, lat] = (feature.geometry as unknown as { coordinates: number[] }).coordinates;\n const isCluster = clusterId !== undefined;\n const key = isCluster ? `c:${clusterId}` : `m:${id}`;\n next.set(key, {\n key,\n position: [lng!, lat!],\n marker: isCluster ? null : String(id),\n cluster: isCluster ? { id: clusterId, count } : null,\n });\n }\n for (const [key, pin] of pins) {\n if (next.has(key)) continue;\n pin.marker.remove();\n pins.delete(key);\n }\n const shown: Visible[] = [];\n for (const [key, entry] of next) {\n let pin = pins.get(key);\n if (!pin) {\n const el = document.createElement('div');\n const marker = new ml.Marker({ element: el }).setLngLat(entry.position).addTo(current);\n // MapLibre labels its marker element a button; the pin inside is the control.\n el.removeAttribute('role');\n el.removeAttribute('aria-label');\n pin = { marker, el };\n pins.set(key, pin);\n }\n shown.push({ ...entry, el: pin.el });\n }\n setVisible(shown);\n };\n\n current.on('style.load', () => {\n addSource();\n sync();\n });\n current.on('sourcedata', (event) => {\n if (event.sourceId === SOURCE && event.isSourceLoaded) sync();\n });\n current.on('moveend', () => {\n sync();\n const { lng, lat } = current.getCenter();\n latest.current.onViewChange?.({ center: [lng, lat], zoom: current.getZoom() });\n });\n\n reveal.current = (id) => {\n const target = latest.current.markers.find((m) => m.id === id);\n if (!target || !isDrawable(target)) return;\n // A marker inside a cluster is not on screen yet: go in far enough to separate it.\n const separate = !pins.has(`m:${id}`);\n current.easeTo({\n center: [target.position[0], target.position[1]],\n zoom: separate ? Math.max(current.getZoom(), CLUSTER_MAX_ZOOM + 1) : current.getZoom(),\n // The popup opens above the pin: keep the pin low enough for it to fit.\n offset: [0, 56],\n });\n };\n refit.current = () => {\n const box = markerBounds(latest.current.markers);\n if (box) current.fitBounds(box, FIT);\n };\n if (latest.current.selectedId) reveal.current(latest.current.selectedId);\n })();\n\n return () => {\n disposed = true;\n reveal.current = undefined;\n refit.current = undefined;\n for (const { marker } of pins.values()) marker.remove();\n pins.clear();\n map?.remove();\n mapRef.current = null;\n setVisible([]);\n };\n }, [interactive, cluster]);\n\n // The accessible name follows the prop without rebuilding the map.\n useEffect(() => {\n ariaLabelRef.current = ariaLabel;\n mapRef.current?.getCanvas().setAttribute('aria-label', ariaLabel);\n }, [ariaLabel]);\n\n // Light and dark styles: the theme class on <html> reaches us through `useCosmosTheme`.\n useEffect(() => {\n if (appliedStyle.current === wantedStyle) return;\n appliedStyle.current = wantedStyle;\n // A full swap: the marker source is added again on `style.load`.\n mapRef.current?.setStyle(wantedStyle, { diff: false });\n }, [wantedStyle]);\n\n // New markers: feed the source, and frame them when the set (not just a field) changed.\n const ids = markers.map((m) => m.id).join('\\u0000');\n const framed = useRef(ids);\n useEffect(() => {\n const source = mapRef.current?.getSource(SOURCE) as GeoJSONSource | undefined;\n source?.setData(markersToGeoJSON(markers));\n }, [markers]);\n useEffect(() => {\n if (framed.current === ids) return;\n framed.current = ids;\n if (fit) refit.current?.();\n }, [ids, fit]);\n\n // A marker chosen from the list or by code: bring it into view and open it.\n useEffect(() => {\n if (selectedId) reveal.current?.(selectedId);\n }, [selectedId]);\n\n // The open pin goes above its neighbours, so its popup is not covered.\n useEffect(() => {\n for (const v of visible) raise(v.el, v.marker === selectedId);\n }, [visible, selectedId]);\n\n // Thrown while rendering, so an error boundary catches it: a missing peer names the package.\n if (failure) throw failure;\n\n const byId = new Map(markers.map((m) => [m.id, m]));\n const zoomBy = (delta: 1 | -1) =>\n delta > 0 ? mapRef.current?.zoomIn() : mapRef.current?.zoomOut();\n\n return (\n <div\n ref={ref}\n className={clsx(\n 'relative isolate w-full overflow-hidden rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-base)]',\n // MapLibre's attribution is a half-white box under the page's text colour, unreadable\n // over dark tiles: it takes the panel and the secondary text, and its links an underline\n // rather than MapLibre's black. `!`: MapLibre's stylesheet is loaded outside the layers.\n '[&_.maplibregl-ctrl-attrib]:bg-[var(--color-bg-panel)]! [&_.maplibregl-ctrl-attrib]:text-[var(--color-text-secondary)] [&_.maplibregl-ctrl-attrib_a]:text-inherit! [&_.maplibregl-ctrl-attrib_a]:underline!',\n className\n )}\n style={{ height }}\n onKeyDown={(event) => {\n if (event.key === 'Escape' && selectedId) {\n event.stopPropagation();\n setSelectedId(null);\n }\n }}\n >\n <div ref={containerRef} className=\"size-full\" />\n\n {unavailable && (\n <p\n role=\"status\"\n className=\"absolute inset-0 flex items-center justify-center p-6 text-center text-sm text-[var(--color-text-secondary)]\"\n >\n {unavailableMessage}\n </p>\n )}\n\n {visible.map((v) => {\n const content = v.cluster ? (\n <ClusterPin\n count={v.cluster.count}\n interactive={interactive}\n onOpen={() => {\n const map = mapRef.current;\n const source = map?.getSource(SOURCE) as GeoJSONSource | undefined;\n const group = v.cluster;\n if (!map || !source || !group) return;\n // Zoom to the level that splits this group, centred on it.\n source.getClusterExpansionZoom(group.id).then(\n (zoom) => map.easeTo({ center: v.position, zoom }),\n () => undefined\n );\n }}\n />\n ) : (\n v.marker !== null &&\n byId.has(v.marker) && (\n <MapPin\n marker={byId.get(v.marker)!}\n selected={v.marker === selectedId}\n interactive={interactive}\n onSelect={setSelectedId}\n onClose={() => setSelectedId(null)}\n />\n )\n );\n return content ? createPortal(content, v.el, v.key) : null;\n })}\n\n {interactive && !unavailable && (\n <div className=\"inset-e-3 absolute top-3 z-10 flex flex-col overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-panel)] shadow-sm\">\n <button\n type=\"button\"\n aria-label=\"Zoom in\"\n onClick={() => zoomBy(1)}\n className=\"focus-visible:focus-indicator size-ctrl-36 flex cursor-pointer items-center justify-center text-[var(--color-text-base)] hover:bg-gray-100 focus-visible:ring-inset dark:hover:bg-white/10\"\n >\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n aria-label=\"Zoom out\"\n onClick={() => zoomBy(-1)}\n className=\"focus-visible:focus-indicator size-ctrl-36 flex cursor-pointer items-center justify-center border-t border-[var(--color-border)] text-[var(--color-text-base)] hover:bg-gray-100 focus-visible:ring-inset dark:hover:bg-white/10\"\n >\n <Minus className=\"size-4\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n </div>\n );\n};\n","import { clsx } from 'clsx';\nimport { MapPin } from 'lucide-react';\nimport type { MapMarker } from './types';\n\nexport type MapMarkerListProps = {\n /** The same array as the map's `markers`. */\n markers: readonly MapMarker[];\n /** The marker that is open on the map. Pass the map's `selectedId`. */\n selectedId?: string | null;\n /** A row was chosen: set the map's `selectedId` to it. */\n onSelect?: (id: string) => void;\n /** Names the list. Default `Places`. */\n ariaLabel?: string;\n className?: string;\n};\n\n/**\n * The markers as a list of buttons, next to the map. A map alone cannot be used with a screen\n * reader or a keyboard beyond panning, so the list is the way to reach each place: choosing a row\n * opens its popup on the map.\n */\nexport const MapMarkerList = ({\n markers,\n selectedId = null,\n onSelect,\n ariaLabel = 'Places',\n className,\n}: MapMarkerListProps) => (\n <ul aria-label={ariaLabel} className={clsx('flex flex-col gap-1', className)}>\n {markers.map(({ id, label, description }) => {\n const selected = id === selectedId;\n return (\n <li key={id}>\n <button\n type=\"button\"\n aria-pressed={selected}\n onClick={() => onSelect?.(id)}\n className={clsx(\n 'focus-visible:focus-indicator py-row-8 flex w-full cursor-pointer items-start gap-3 rounded-lg border px-3 text-start text-sm',\n selected\n ? 'border-brand-300 bg-brand-50 dark:border-brand-500/40 dark:bg-brand-500/10'\n : 'border-transparent hover:bg-gray-100 dark:hover:bg-white/5'\n )}\n >\n <MapPin\n className={clsx(\n 'mt-0.5 size-4 shrink-0',\n selected\n ? 'text-brand-600 dark:text-brand-400'\n : 'text-[var(--color-text-secondary)]'\n )}\n aria-hidden=\"true\"\n />\n <span className=\"min-w-0\">\n <span className=\"block font-medium text-[var(--color-text-base)]\">\n <bdi>{label}</bdi>\n </span>\n {description != null && (\n <span className=\"block text-xs text-[var(--color-text-secondary)]\">\n {description}\n </span>\n )}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n);\n"]}
@@ -0,0 +1,95 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, Ref } from 'react';
3
+ import { StyleSpecification } from 'maplibre-gl';
4
+ import { Intent } from '../index.cjs';
5
+
6
+ /** A place on the map. The same array feeds `MapView` and `MapMarkerList`. */
7
+ type MapMarker = {
8
+ /** Identifies the marker in `selectedId` and in the list. Unique in the array. */
9
+ id: string;
10
+ /** `[longitude, latitude]`, in degrees (the GeoJSON order). A marker outside the range is not drawn. */
11
+ position: readonly [lng: number, lat: number];
12
+ /** The name: the accessible name of the pin, the title of the popup and the text of the list row. */
13
+ label: string;
14
+ /** Shown under the title in the popup and in the list. Plain React: markup in data is text. */
15
+ description?: ReactNode;
16
+ /** The pin's colour. Default `brand`. */
17
+ intent?: Intent;
18
+ /** An icon, or a short text such as a number, drawn in the pin. */
19
+ icon?: ReactNode;
20
+ };
21
+
22
+ type MapViewState = {
23
+ /** `[longitude, latitude]` of the middle of the map. */
24
+ center: [lng: number, lat: number];
25
+ zoom: number;
26
+ };
27
+ type MapViewProps = {
28
+ /** The places to show. Define it once (state, memo or a constant): a new array redraws the pins. */
29
+ markers: readonly MapMarker[];
30
+ /**
31
+ * The light map style: a MapLibre style URL or object. Cosmos ships none, since a tile server has
32
+ * its own terms and sees every visitor. Pass a constant, not a new object on each render.
33
+ */
34
+ style: string | StyleSpecification;
35
+ /** The style under the dark themes (`.dark`, `.deep-blue`). Without it the light one is kept. */
36
+ darkStyle?: string | StyleSpecification;
37
+ /**
38
+ * `interactive` pans and zooms, takes the focus and shows the zoom buttons. `static` is a fixed
39
+ * view for a card or a detail page: no gesture, no focus stop, no popup, and the pins are
40
+ * pictures, so give the places to assistive technology with a `MapMarkerList`.
41
+ */
42
+ variant?: 'interactive' | 'static';
43
+ /** Names the map for assistive technology. Default `Map`. */
44
+ ariaLabel?: string;
45
+ /** Height of the map, in pixels or any CSS length. Default 360. */
46
+ height?: number | string;
47
+ /** Frame the markers on the first draw, and again when the set of markers changes. Default `true`. */
48
+ fit?: boolean;
49
+ /** The view when `fit` is off or there is no marker. Not controlled: the map owns it afterwards. */
50
+ initialCenter?: [lng: number, lat: number];
51
+ initialZoom?: number;
52
+ /** Group the markers that are close at the current zoom into a counted circle. Default `true`. */
53
+ cluster?: boolean;
54
+ /** The marker whose popup is open, `null` for none. */
55
+ selectedId?: string | null;
56
+ defaultSelectedId?: string | null;
57
+ onSelectedIdChange?: (id: string | null) => void;
58
+ /** The view after each pan or zoom, however it was triggered (pointer, keyboard, buttons). */
59
+ onViewChange?: (view: MapViewState) => void;
60
+ /** Shown in place of the map when the browser cannot draw it (no WebGL). */
61
+ unavailableMessage?: string;
62
+ className?: string;
63
+ ref?: Ref<HTMLDivElement>;
64
+ };
65
+ /**
66
+ * An interactive map of places, drawn with MapLibre GL JS (the optional peer `maplibre-gl`): pins
67
+ * in Cosmos colours with an icon or a number, a popup per pin, clusters, zoom buttons and a fit to
68
+ * the markers. Load MapLibre's stylesheet once in the app: `import 'maplibre-gl/dist/maplibre-gl.css'`.
69
+ *
70
+ * The pins, popups and buttons are React and HTML, so a title or description with markup shows as
71
+ * text and the theme repaints them on its own. Only the base map (`style` / `darkStyle`) comes from
72
+ * the host.
73
+ */
74
+ declare const MapView: ({ markers, style, darkStyle, variant, ariaLabel, height, fit, initialCenter, initialZoom, cluster, selectedId: selectedIdProp, defaultSelectedId, onSelectedIdChange, onViewChange, unavailableMessage, className, ref, }: MapViewProps) => react.JSX.Element;
75
+
76
+ type MapMarkerListProps = {
77
+ /** The same array as the map's `markers`. */
78
+ markers: readonly MapMarker[];
79
+ /** The marker that is open on the map. Pass the map's `selectedId`. */
80
+ selectedId?: string | null;
81
+ /** A row was chosen: set the map's `selectedId` to it. */
82
+ onSelect?: (id: string) => void;
83
+ /** Names the list. Default `Places`. */
84
+ ariaLabel?: string;
85
+ className?: string;
86
+ };
87
+ /**
88
+ * The markers as a list of buttons, next to the map. A map alone cannot be used with a screen
89
+ * reader or a keyboard beyond panning, so the list is the way to reach each place: choosing a row
90
+ * opens its popup on the map.
91
+ */
92
+ declare const MapMarkerList: ({ markers, selectedId, onSelect, ariaLabel, className, }: MapMarkerListProps) => react.JSX.Element;
93
+
94
+ export { MapMarkerList, MapView };
95
+ export type { MapMarker, MapMarkerListProps, MapViewProps, MapViewState };
@@ -0,0 +1,95 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, Ref } from 'react';
3
+ import { StyleSpecification } from 'maplibre-gl';
4
+ import { Intent } from '../index.js';
5
+
6
+ /** A place on the map. The same array feeds `MapView` and `MapMarkerList`. */
7
+ type MapMarker = {
8
+ /** Identifies the marker in `selectedId` and in the list. Unique in the array. */
9
+ id: string;
10
+ /** `[longitude, latitude]`, in degrees (the GeoJSON order). A marker outside the range is not drawn. */
11
+ position: readonly [lng: number, lat: number];
12
+ /** The name: the accessible name of the pin, the title of the popup and the text of the list row. */
13
+ label: string;
14
+ /** Shown under the title in the popup and in the list. Plain React: markup in data is text. */
15
+ description?: ReactNode;
16
+ /** The pin's colour. Default `brand`. */
17
+ intent?: Intent;
18
+ /** An icon, or a short text such as a number, drawn in the pin. */
19
+ icon?: ReactNode;
20
+ };
21
+
22
+ type MapViewState = {
23
+ /** `[longitude, latitude]` of the middle of the map. */
24
+ center: [lng: number, lat: number];
25
+ zoom: number;
26
+ };
27
+ type MapViewProps = {
28
+ /** The places to show. Define it once (state, memo or a constant): a new array redraws the pins. */
29
+ markers: readonly MapMarker[];
30
+ /**
31
+ * The light map style: a MapLibre style URL or object. Cosmos ships none, since a tile server has
32
+ * its own terms and sees every visitor. Pass a constant, not a new object on each render.
33
+ */
34
+ style: string | StyleSpecification;
35
+ /** The style under the dark themes (`.dark`, `.deep-blue`). Without it the light one is kept. */
36
+ darkStyle?: string | StyleSpecification;
37
+ /**
38
+ * `interactive` pans and zooms, takes the focus and shows the zoom buttons. `static` is a fixed
39
+ * view for a card or a detail page: no gesture, no focus stop, no popup, and the pins are
40
+ * pictures, so give the places to assistive technology with a `MapMarkerList`.
41
+ */
42
+ variant?: 'interactive' | 'static';
43
+ /** Names the map for assistive technology. Default `Map`. */
44
+ ariaLabel?: string;
45
+ /** Height of the map, in pixels or any CSS length. Default 360. */
46
+ height?: number | string;
47
+ /** Frame the markers on the first draw, and again when the set of markers changes. Default `true`. */
48
+ fit?: boolean;
49
+ /** The view when `fit` is off or there is no marker. Not controlled: the map owns it afterwards. */
50
+ initialCenter?: [lng: number, lat: number];
51
+ initialZoom?: number;
52
+ /** Group the markers that are close at the current zoom into a counted circle. Default `true`. */
53
+ cluster?: boolean;
54
+ /** The marker whose popup is open, `null` for none. */
55
+ selectedId?: string | null;
56
+ defaultSelectedId?: string | null;
57
+ onSelectedIdChange?: (id: string | null) => void;
58
+ /** The view after each pan or zoom, however it was triggered (pointer, keyboard, buttons). */
59
+ onViewChange?: (view: MapViewState) => void;
60
+ /** Shown in place of the map when the browser cannot draw it (no WebGL). */
61
+ unavailableMessage?: string;
62
+ className?: string;
63
+ ref?: Ref<HTMLDivElement>;
64
+ };
65
+ /**
66
+ * An interactive map of places, drawn with MapLibre GL JS (the optional peer `maplibre-gl`): pins
67
+ * in Cosmos colours with an icon or a number, a popup per pin, clusters, zoom buttons and a fit to
68
+ * the markers. Load MapLibre's stylesheet once in the app: `import 'maplibre-gl/dist/maplibre-gl.css'`.
69
+ *
70
+ * The pins, popups and buttons are React and HTML, so a title or description with markup shows as
71
+ * text and the theme repaints them on its own. Only the base map (`style` / `darkStyle`) comes from
72
+ * the host.
73
+ */
74
+ declare const MapView: ({ markers, style, darkStyle, variant, ariaLabel, height, fit, initialCenter, initialZoom, cluster, selectedId: selectedIdProp, defaultSelectedId, onSelectedIdChange, onViewChange, unavailableMessage, className, ref, }: MapViewProps) => react.JSX.Element;
75
+
76
+ type MapMarkerListProps = {
77
+ /** The same array as the map's `markers`. */
78
+ markers: readonly MapMarker[];
79
+ /** The marker that is open on the map. Pass the map's `selectedId`. */
80
+ selectedId?: string | null;
81
+ /** A row was chosen: set the map's `selectedId` to it. */
82
+ onSelect?: (id: string) => void;
83
+ /** Names the list. Default `Places`. */
84
+ ariaLabel?: string;
85
+ className?: string;
86
+ };
87
+ /**
88
+ * The markers as a list of buttons, next to the map. A map alone cannot be used with a screen
89
+ * reader or a keyboard beyond panning, so the list is the way to reach each place: choosing a row
90
+ * opens its popup on the map.
91
+ */
92
+ declare const MapMarkerList: ({ markers, selectedId, onSelect, ariaLabel, className, }: MapMarkerListProps) => react.JSX.Element;
93
+
94
+ export { MapMarkerList, MapView };
95
+ export type { MapMarker, MapMarkerListProps, MapViewProps, MapViewState };