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.
154 lines
5.1 KiB
TypeScript
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;
|