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.
leaflet-components/src/core/shared-props.ts

154 lines
5.1 KiB
TypeScript

import type {
CrossOrigin,
FillRule,
LatLng,
LatLngBounds,
LatLngBoundsExpression,
LineCapShape,
LineJoinShape,
PathOptions,
ReferrerPolicy,
} from 'leaflet';
import { bool, choice, json, num, positional, str, type PropDef } from './props.ts';
// Leaflet classes share no common interface, so the prop fragments below
// describe structurally what they need from the object they update.
export interface Positioned {
getLatLng(): LatLng | undefined;
setLatLng(latlng: [number, number]): unknown;
}
export interface Styleable {
setStyle(style: PathOptions): unknown;
}
export interface Sourced {
setUrl(url: string): unknown;
}
export interface Bounded {
getBounds(): LatLngBounds;
}
// Shared `get` for any `bounds` prop backed by Leaflet's getBounds(). Returns
// a plain [[south, west], [north, east]] pair rather than the LatLngBounds
// instance, matching the JSON-encoded shape the attribute round-trips through.
export function getBounds(obj: Bounded): LatLngBoundsExpression {
const b = obj.getBounds();
return [
[b.getSouth(), b.getWest()],
[b.getNorth(), b.getEast()],
];
}
interface PositionedHost extends HTMLElement {
lat: number;
lng: number;
}
// A `set` for any option Leaflet only exposes through setStyle().
export function style<T>(key: keyof PathOptions): (obj: Styleable, value: T) => void {
return (obj, value) => {
obj.setStyle({ [key]: value } as PathOptions);
};
}
// The source url. Passed positionally by every Leaflet constructor that takes
// one, and ignored when blank so clearing the attribute can't request nothing.
// No `get`: none of TileLayer/ImageOverlay/VideoOverlay expose a getUrl().
export const urlProp = positional(
str<Sourced>('', {
set(obj, value) {
if (value) obj.setUrl(value);
},
}),
);
// lat/lng travel together: both are passed positionally to the Leaflet
// constructor rather than as options, setting either re-issues setLatLng with
// the other's current value, and both are written back whenever the object
// moves -- which is what keeps the attributes current while a marker is
// dragged. Shared by marker, circle, circle-marker, popup and tooltip.
export const latLngProps = {
lat: positional(
num<Positioned>(0, {
event: 'move',
get: (obj) => obj.getLatLng()?.lat,
set(obj, value, el) {
obj.setLatLng([value, (el as PositionedHost).lng]);
},
}),
),
lng: positional(
num<Positioned>(0, {
event: 'move',
get: (obj) => obj.getLatLng()?.lng,
set(obj, value, el) {
obj.setLatLng([(el as PositionedHost).lat, value]);
},
}),
),
} as const;
// The style options every Path accepts. Defaults match Leaflet's own, so an
// absent attribute and an unset option mean the same thing.
export const pathProps = {
stroke: bool<Styleable>(true, { set: style('stroke') }),
color: str<Styleable>('#3388ff', { set: style('color') }),
weight: num<Styleable>(3, { set: style('weight') }),
opacity: num<Styleable>(1.0, { set: style('opacity') }),
lineCap: choice<LineCapShape, Styleable>('round', { set: style('lineCap') }),
lineJoin: choice<LineJoinShape, Styleable>('round', { set: style('lineJoin') }),
dashArray: str<Styleable>('', { set: style('dashArray') }),
dashOffset: str<Styleable>('', { set: style('dashOffset') }),
fill: bool<Styleable>(true, { set: style('fill') }),
fillColor: str<Styleable>('#3388ff', { set: style('fillColor') }),
fillOpacity: num<Styleable>(0.2, { set: style('fillOpacity') }),
fillRule: choice<FillRule, Styleable>('evenodd', { set: style('fillRule') }),
// Constructor-only: Leaflet has no setter for these, so changing the
// attribute after creation has no effect (same as leaflet-map's zoomSnap).
className: str(),
interactive: bool(true),
bubblingMouseEvents: bool(true),
pane: str('overlay'),
} as const;
// The GridLayer/TileLayer options every tile source accepts, shared by
// leaflet-tile-layer and leaflet-tile-layer-wms (WMSOptions extends
// TileLayerOptions). Most are constructor-only -- Leaflet exposes no setter
// for them -- so changing the attribute after creation has no effect, same as
// leaflet-map's zoomSnap.
export const tileLayerProps = {
attribution: str(),
minZoom: num(0),
maxZoom: num(18),
opacity: num(1.0),
zIndex: num(1),
subdomains: str('abc'),
tms: bool(),
zoomOffset: num(0),
zoomReverse: bool(),
detectRetina: bool(),
crossOrigin: choice<CrossOrigin>(''),
// Leaflet's ReferrerPolicy type has no "unset" member (unlike the DOM's),
// so this is a plain PropDef rather than choice(), with `undefined` as the
// fallback the property getter reports when the attribute is absent.
referrerPolicy: {
default: undefined,
decode: (raw) => raw as ReferrerPolicy,
encode: (value) => value ?? null,
} as PropDef<ReferrerPolicy | undefined>,
errorTileUrl: str(),
tileSize: num(256),
noWrap: bool(),
bounds: json<LatLngBoundsExpression>([]),
className: str(),
minNativeZoom: num(),
maxNativeZoom: num(),
keepBuffer: num(2),
updateWhenIdle: bool(),
updateWhenZooming: bool(true),
updateInterval: num(200),
pane: str('tilePane'),
} as const;