// Every element property is described by a PropDef: how its value encodes to // and decodes from an HTML attribute, and how it is pushed into (and read back // out of) the Leaflet object. `WithProps` in with-props.ts is the only consumer // -- components just declare a table of these and never touch the plumbing. export interface PropDef { // Attribute name. Defaults to the kebab-cased property name. A function // receives the property name and returns the attribute (see `disabled`). attribute?: string | ((name: string) => string); // The property value when the attribute is absent. Keep this equal to // Leaflet's own default: an absent attribute is left out of the options // object entirely, so it is Leaflet's default that actually takes effect. default: T; // Set through `positional()` for values the Leaflet constructor takes as an // argument (coordinates, urls, bounds) rather than as an option. option?: false; decode(raw: string): T; // Returning null removes the attribute, which restores Leaflet's default. encode(value: T): string | null; // Pushes a new value into the Leaflet object. Defaults to calling the // matching setter when the object has one (`opacity` -> `setOpacity`). set?(obj: TObj, value: T, el: HTMLElement): void; // Reads the live value back out of the Leaflet object. Used by the property // getter, and by `event` below to write the value back to the attribute. get?(obj: TObj): T | undefined; // Leaflet event after which `get` is re-read and synced to the attribute, // e.g. `move` keeps lat/lng current while a marker is dragged. event?: string; } export type PropTable = Record>; // Everything a codec factory doesn't fill in for you. `option` is not here: // it has to come from `positional()` to be visible in PropOptionValues. export type PropOptions = Partial< Omit, 'default' | 'decode' | 'encode' | 'option'> >; // The value type of a single prop, and of a whole table. export type PropValue

= P extends { default: infer T } ? T : never; export type PropValues = { [K in keyof T]: PropValue }; // The options object handed to `createLeafletObject`. Partial because a prop // only appears when its attribute is present; `option: false` props never do. export type PropOptionValues = Partial<{ [K in keyof T as T[K] extends { option: false } ? never : K]: PropValue; }>; // The type `positional()` produces: a PropDef flagged so #buildOptions skips // it. Spelled out as an alias so element PROPS tables can be given the explicit // type annotations JSR's "no slow types" check requires without repeating the // intersection everywhere. export type Positional = PropDef & { option: false }; // Marks a prop the Leaflet constructor takes as an argument, so it is left out // of the options object handed to createLeafletObject(). export function positional(def: T): T & { option: false } { return { ...def, option: false }; } export function kebab(name: string): string { return name.replaceAll(/[A-Z]/gu, (c) => `-${c.toLowerCase()}`); } export function num( def = 0, opts?: PropOptions, ): PropDef { return { default: def, decode: Number, encode: String, ...opts }; } export function str( def = '', opts?: PropOptions, ): PropDef { return { default: def, decode: (raw) => raw, encode: (value) => value, ...opts }; } // A string attribute whose values Leaflet types as a union -- ControlPosition, // CrossOrigin, tooltip Direction. Nothing is validated at runtime; this is how // the options object comes out with the type Leaflet's constructor expects. export function choice( def: T, opts?: PropOptions, ): PropDef { return { default: def, decode: (raw) => raw as T, encode: (value) => value, ...opts }; } // A boolean attribute: present is true, `="false"` is false, absent is `def`. // Use `bool(true)` for options Leaflet already defaults to true, so that // `` can turn them off. export function bool( def = false, opts?: PropOptions, ): PropDef { return { default: def, decode: (raw) => raw !== 'false', encode: (value) => (value === def ? null : value ? '' : 'false'), ...opts, }; } // The inverse of `bool(true)`: `` reads as // `dragging === false`. Named `disable-` unless `attribute` says else. export function disabled( opts?: PropOptions, ): PropDef { return { attribute: (name) => `disable-${kebab(name)}`, default: true, decode: (raw) => raw === 'false', encode: (value) => (value ? null : ''), ...opts, }; } // For attributes holding JSON: bounds, icon sizes and anchors, GeoJSON data. export function json(def: T, opts?: PropOptions): PropDef { return { default: def, decode: (raw) => JSON.parse(raw) as T, encode: (value) => JSON.stringify(value), ...opts, }; }