@florasync/leaflet-geokit 0.2.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.
- package/CHANGELOG.md +12 -0
- package/README.md +666 -59
- package/dist/django/index.js +9517 -0
- package/dist/django/index.js.map +1 -0
- package/dist/leaflet-geokit.es.js +2039 -1871
- package/dist/leaflet-geokit.es.js.map +1 -1
- package/dist/leaflet-geokit.external.es.js +2201 -0
- package/dist/leaflet-geokit.external.es.js.map +1 -0
- package/dist/leaflet-geokit.umd.js +36 -16
- package/dist/leaflet-geokit.umd.js.map +1 -1
- package/dist/preact/index-BM2U4rKn.js +2188 -0
- package/dist/preact/index-BM2U4rKn.js.map +1 -0
- package/dist/preact/index.js +109 -0
- package/dist/preact/index.js.map +1 -0
- package/dist/preact-bundled/index.js +9496 -0
- package/dist/preact-bundled/index.js.map +1 -0
- package/dist/react/index-BM2U4rKn.js +2188 -0
- package/dist/react/index-BM2U4rKn.js.map +1 -0
- package/dist/react/index.js +109 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react-bundled/index.js +9496 -0
- package/dist/react-bundled/index.js.map +1 -0
- package/dist/types/e2e/component.spec.d.ts +1 -0
- package/dist/types/e2e/dummy.spec.d.ts +1 -0
- package/dist/types/src/components/LeafletDrawMapElement.d.ts +94 -0
- package/dist/types/src/django/index.d.ts +35 -0
- package/dist/types/src/external.d.ts +4 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/lib/FeatureStore.d.ts +48 -0
- package/dist/types/src/lib/MapController.d.ts +116 -0
- package/dist/types/src/lib/draw/L.Draw.Cake.d.ts +9 -0
- package/dist/types/src/lib/draw/toolbar-patch.d.ts +2 -0
- package/dist/types/src/lib/layer-cake/CakeBaker.d.ts +12 -0
- package/dist/types/src/lib/layer-cake/LayerCakeManager.d.ts +29 -0
- package/dist/types/src/lib/layer-cake/bindCakeControls.d.ts +8 -0
- package/dist/types/src/lib/layer-cake/ensureCircleEditable.d.ts +2 -0
- package/dist/types/src/lib/leaflet-assets.d.ts +23 -0
- package/dist/types/src/preact/core.d.ts +36 -0
- package/dist/types/src/preact/index.d.ts +8 -0
- package/dist/types/src/preact-bundled/index.d.ts +9 -0
- package/dist/types/src/react/core.d.ts +36 -0
- package/dist/types/src/react/index.d.ts +7 -0
- package/dist/types/src/react-bundled/index.d.ts +8 -0
- package/dist/types/src/shims/ensure-element.d.ts +2 -0
- package/dist/types/src/state/types.d.ts +7 -0
- package/dist/types/src/types/events.d.ts +71 -0
- package/dist/types/src/types/public.d.ts +106 -0
- package/dist/types/src/utils/geodesic.d.ts +8 -0
- package/dist/types/src/utils/geojson.d.ts +70 -0
- package/dist/types/src/utils/leaflet-guards.d.ts +9 -0
- package/dist/types/src/utils/logger.d.ts +12 -0
- package/dist/types/src/utils/ruler.d.ts +31 -0
- package/dist/types/tests/bbox-more.spec.d.ts +1 -0
- package/dist/types/tests/component-api-more.spec.d.ts +1 -0
- package/dist/types/tests/component-delegation.spec.d.ts +1 -0
- package/dist/types/tests/component-events.spec.d.ts +1 -0
- package/dist/types/tests/component-io.spec.d.ts +1 -0
- package/dist/types/tests/django-shim.spec.d.ts +1 -0
- package/dist/types/tests/draw-cake.spec.d.ts +1 -0
- package/dist/types/tests/eachcoord.spec.d.ts +1 -0
- package/dist/types/tests/element.spec.d.ts +1 -0
- package/dist/types/tests/featureStore-more.spec.d.ts +1 -0
- package/dist/types/tests/featureStore.spec.d.ts +1 -0
- package/dist/types/tests/framework-runtime-externalization.spec.d.ts +1 -0
- package/dist/types/tests/geodesic.spec.d.ts +1 -0
- package/dist/types/tests/geojson-merge.spec.d.ts +1 -0
- package/dist/types/tests/geojson-more.spec.d.ts +1 -0
- package/dist/types/tests/geojson.spec.d.ts +1 -0
- package/dist/types/tests/layer-cake-baker.spec.d.ts +1 -0
- package/dist/types/tests/layer-cake-controls.spec.d.ts +1 -0
- package/dist/types/tests/layer-cake-editing.spec.d.ts +1 -0
- package/dist/types/tests/layer-cake-manager.spec.d.ts +1 -0
- package/dist/types/tests/leaflet-assets.spec.d.ts +1 -0
- package/dist/types/tests/leaflet-draw-circle-resize-patch.spec.d.ts +1 -0
- package/dist/types/tests/logger-more.spec.d.ts +1 -0
- package/dist/types/tests/logger.spec.d.ts +1 -0
- package/dist/types/tests/map-controller.spec.d.ts +1 -0
- package/dist/types/tests/mapcontroller-merge.spec.d.ts +1 -0
- package/dist/types/tests/preact-bundled-shim.spec.d.ts +1 -0
- package/dist/types/tests/react-bundled-shim.spec.d.ts +1 -0
- package/dist/types/tests/react-shim.spec.d.ts +1 -0
- package/dist/types/tests/ruler.spec.d.ts +1 -0
- package/dist/types/vite.config.d.ts +2 -0
- package/dist/types/vite.config.external.d.ts +2 -0
- package/dist/types/vite.config.preact-bundled.d.ts +2 -0
- package/dist/types/vite.config.preact.d.ts +2 -0
- package/dist/types/vite.config.react-bundled.d.ts +2 -0
- package/dist/types/vite.config.react.d.ts +2 -0
- package/package.json +61 -5
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ Documentation quick-links
|
|
|
30
30
|
- Runtime and architecture overview
|
|
31
31
|
- Public API (attributes, properties, methods, events)
|
|
32
32
|
- Usage examples (HTML, ESM, framework integration, recipes)
|
|
33
|
+
- Framework Support
|
|
33
34
|
- Logging, diagnostics, and troubleshooting
|
|
34
35
|
- Performance, accessibility, and SSR notes
|
|
35
36
|
- Roadmap and versioning
|
|
@@ -68,6 +69,43 @@ For consumption from another app:
|
|
|
68
69
|
- `npm install @florasync/leaflet-geokit`
|
|
69
70
|
- `import "@florasync/leaflet-geokit";` and ensure the element is sized via CSS (it does not self-size)
|
|
70
71
|
|
|
72
|
+
### Bundled vs external Leaflet/Leaflet.draw
|
|
73
|
+
|
|
74
|
+
We ship two entrypoints so you can avoid double-loading Leaflet if your app already includes it.
|
|
75
|
+
|
|
76
|
+
- **Bundled (default)** — includes Leaflet + Leaflet.draw JS/CSS inside the library bundle. Use this when you are _not_ providing Leaflet yourself.
|
|
77
|
+
- Import: `import "@florasync/leaflet-geokit";`
|
|
78
|
+
|
|
79
|
+
- **External (no Leaflet bytes)** — expects Leaflet + Leaflet.draw + CSS to be provided by the host (e.g., your framework already loads them).
|
|
80
|
+
- Import: `import "@florasync/leaflet-geokit/external";`
|
|
81
|
+
- Requires `window.L` with `L.Control.Draw` available, and Leaflet/Draw CSS loaded by you.
|
|
82
|
+
- If external `L` is present but Draw APIs are incomplete, runtime falls back to bundled Leaflet/Draw and logs a warning.
|
|
83
|
+
|
|
84
|
+
Runtime options (advanced)
|
|
85
|
+
|
|
86
|
+
- Programmatic flag: `map.useExternalLeaflet = true` to prefer a provided `window.L` (falls back to bundled if missing). See [LeafletDrawMapElement](src/components/LeafletDrawMapElement.ts:15) and [MapController](src/lib/MapController.ts:41).
|
|
87
|
+
- Programmatic flag: `map.skipLeafletStyles = true` to disable our CSS/icon injection when you own the styles. Styling helpers live in [leaflet-assets](src/lib/leaflet-assets.ts:1).
|
|
88
|
+
|
|
89
|
+
Example: external entrypoint in a bundler app
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import "leaflet/dist/leaflet.css";
|
|
93
|
+
import "leaflet-draw/dist/leaflet.draw.css";
|
|
94
|
+
import "@florasync/leaflet-geokit/external";
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then mount your element as usual:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<leaflet-geokit draw-polygon draw-marker></leaflet-geokit>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Why choose external?
|
|
104
|
+
|
|
105
|
+
- Avoid double-loading Leaflet/Leaflet.draw when your host already ships them.
|
|
106
|
+
- Keep bundle size smaller in hosts that vendor Leaflet themselves.
|
|
107
|
+
- Control Leaflet/Draw versions and theming directly.
|
|
108
|
+
|
|
71
109
|
---
|
|
72
110
|
|
|
73
111
|
## Build and compilation
|
|
@@ -86,12 +124,29 @@ Scripts (see [package.json](package.json))
|
|
|
86
124
|
|
|
87
125
|
- npm run dev — Vite dev server
|
|
88
126
|
- npm run build — type declarations + Vite build
|
|
127
|
+
- npm run build:analyze — generate per-target bundle reports in dist/stats/\*.html
|
|
89
128
|
- npm run test:unit — Vitest (happy-dom)
|
|
90
129
|
- npm run typecheck — TypeScript noEmit
|
|
91
130
|
- npm run lint — ESLint (strict TS rules)
|
|
92
131
|
- npm run format — Prettier write
|
|
93
132
|
- npm run test:e2e — Playwright (currently a minimal smoke test under e2e/)
|
|
94
133
|
|
|
134
|
+
Bundle analysis
|
|
135
|
+
|
|
136
|
+
- The analyzer is opt-in and does not change normal production outputs.
|
|
137
|
+
- Run `npm run build:analyze` to produce one report per target:
|
|
138
|
+
- dist/stats/main.html
|
|
139
|
+
- dist/stats/external.html
|
|
140
|
+
- dist/stats/django.html
|
|
141
|
+
- dist/stats/preact.html
|
|
142
|
+
- dist/stats/preact-bundled.html
|
|
143
|
+
- dist/stats/react.html
|
|
144
|
+
- dist/stats/react-bundled.html
|
|
145
|
+
- Read report metrics as:
|
|
146
|
+
- Raw: uncompressed parsed bundle bytes
|
|
147
|
+
- Gzip: transfer cost with gzip compression
|
|
148
|
+
- Brotli: transfer cost with brotli compression
|
|
149
|
+
|
|
95
150
|
Browser support
|
|
96
151
|
|
|
97
152
|
- ES2019, evergreen browsers (Chromium, Firefox, Safari modern)
|
|
@@ -128,65 +183,502 @@ CSS and assets:
|
|
|
128
183
|
|
|
129
184
|
## Public API
|
|
130
185
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
The `<leaflet-geokit>` custom element provides a comprehensive API for embedding interactive maps with drawing capabilities. All functionality is exposed through HTML attributes, JavaScript properties, methods, and events.
|
|
187
|
+
|
|
188
|
+
### HTML Attributes
|
|
189
|
+
|
|
190
|
+
#### Map Configuration
|
|
191
|
+
|
|
192
|
+
Configure the initial map view and tile layer:
|
|
193
|
+
|
|
194
|
+
- **`latitude`** (string number): Initial map latitude coordinate. Default: `"0"`
|
|
195
|
+
- **`longitude`** (string number): Initial map longitude coordinate. Default: `"0"`
|
|
196
|
+
- **`zoom`** (string number): Initial zoom level. Default: `"2"`
|
|
197
|
+
- **`min-zoom`** (string number, optional): Minimum allowed zoom level
|
|
198
|
+
- **`max-zoom`** (string number, optional): Maximum allowed zoom level
|
|
199
|
+
- **`tile-url`** (string): Tile server URL template with `{z}`, `{x}`, `{y}` placeholders. Default: OpenStreetMap
|
|
200
|
+
- **`tile-attribution`** (string, optional): Attribution text for the tile layer
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<leaflet-geokit
|
|
204
|
+
latitude="39.7392"
|
|
205
|
+
longitude="-104.9903"
|
|
206
|
+
zoom="11"
|
|
207
|
+
min-zoom="8"
|
|
208
|
+
max-zoom="18"
|
|
209
|
+
tile-url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
210
|
+
tile-attribution="© OpenStreetMap contributors"
|
|
211
|
+
></leaflet-geokit>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Drawing Tools
|
|
215
|
+
|
|
216
|
+
Enable specific drawing tools by adding boolean attributes (presence = enabled):
|
|
217
|
+
|
|
218
|
+
- **`draw-polygon`**: Enable polygon drawing tool
|
|
219
|
+
- **`draw-polyline`**: Enable polyline/line drawing tool
|
|
220
|
+
- **`draw-rectangle`**: Enable rectangle drawing tool
|
|
221
|
+
- **`draw-circle`**: Enable circle drawing tool
|
|
222
|
+
- **`draw-layer-cake`**: Enable Layer Cake tool for creating concentric donut polygons
|
|
223
|
+
- **`draw-marker`**: Enable point marker drawing tool
|
|
224
|
+
- **`draw-ruler`**: Enable measurement/ruler tool for distances and areas
|
|
225
|
+
|
|
226
|
+
```html
|
|
227
|
+
<leaflet-geokit
|
|
228
|
+
draw-polygon
|
|
229
|
+
draw-polyline
|
|
230
|
+
draw-rectangle
|
|
231
|
+
draw-circle
|
|
232
|
+
draw-layer-cake
|
|
233
|
+
draw-marker
|
|
234
|
+
draw-ruler
|
|
235
|
+
></leaflet-geokit>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Editing Controls
|
|
239
|
+
|
|
240
|
+
Control editing and deletion of existing features:
|
|
241
|
+
|
|
242
|
+
- **`edit-features`**: Enable editing mode for modifying existing shapes
|
|
243
|
+
- **`delete-features`**: Enable deletion mode for removing shapes
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<leaflet-geokit draw-polygon edit-features delete-features></leaflet-geokit>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Behavior Modifiers
|
|
250
|
+
|
|
251
|
+
- **`read-only`**: Disables all drawing, editing, and deleting tools. Map becomes view-only
|
|
252
|
+
- **`polygon-allow-intersection`**: Allows polygons to intersect/overlap during drawing
|
|
253
|
+
- **`prefer-canvas`**: Use Canvas rendering instead of SVG for better performance with large datasets. Default: `true`
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
<leaflet-geokit read-only></leaflet-geokit>
|
|
257
|
+
<leaflet-geokit draw-polygon polygon-allow-intersection></leaflet-geokit>
|
|
258
|
+
<leaflet-geokit prefer-canvas="false"></leaflet-geokit>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Theming & Styling
|
|
262
|
+
|
|
263
|
+
Customize the visual appearance:
|
|
264
|
+
|
|
265
|
+
- **`theme-url`** (string, optional): External CSS stylesheet URL to inject into Shadow DOM
|
|
266
|
+
- **`themeCss`** (property only): Inline CSS strings for custom styling
|
|
267
|
+
|
|
268
|
+
```html
|
|
269
|
+
<leaflet-geokit theme-url="/css/custom-map-theme.css"></leaflet-geokit>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Debugging & Development
|
|
273
|
+
|
|
274
|
+
- **`log-level`** (string): Control console logging verbosity. Options: `trace`, `debug`, `info`, `warn`, `error`, `silent`. Default: `"debug"`
|
|
275
|
+
- **`dev-overlay`** (boolean): Reserved for future development overlay features
|
|
276
|
+
|
|
277
|
+
```html
|
|
278
|
+
<leaflet-geokit log-level="info"></leaflet-geokit>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### JavaScript Properties
|
|
282
|
+
|
|
283
|
+
All attributes have corresponding JavaScript properties for runtime access and modification:
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
const map = document.querySelector("leaflet-geokit");
|
|
287
|
+
|
|
288
|
+
// Map configuration
|
|
289
|
+
map.latitude = 40.7128; // number
|
|
290
|
+
map.longitude = -74.006; // number
|
|
291
|
+
map.zoom = 12; // number
|
|
292
|
+
map.minZoom = 8; // number | undefined
|
|
293
|
+
map.maxZoom = 18; // number | undefined
|
|
294
|
+
map.tileUrl = "https://..."; // string
|
|
295
|
+
map.tileAttribution = "..."; // string | undefined
|
|
296
|
+
|
|
297
|
+
// Behavior
|
|
298
|
+
map.readOnly = true; // boolean
|
|
299
|
+
map.preferCanvas = false; // boolean
|
|
300
|
+
map.logLevel = "info"; // LogLevel
|
|
301
|
+
|
|
302
|
+
// Theming
|
|
303
|
+
map.themeCss = `
|
|
304
|
+
.leaflet-container { font-family: "Inter", sans-serif; }
|
|
305
|
+
.leaflet-draw-toolbar a { border-radius: 8px; }
|
|
306
|
+
`;
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Methods
|
|
310
|
+
|
|
311
|
+
All methods return Promises and should be awaited. Invoke on the element instance:
|
|
312
|
+
|
|
313
|
+
#### Data Management
|
|
314
|
+
|
|
315
|
+
**[`getGeoJSON()`](src/components/LeafletDrawMapElement.ts:384)**: Promise<FeatureCollection>
|
|
316
|
+
|
|
317
|
+
- Returns current map data as a GeoJSON FeatureCollection
|
|
318
|
+
- Includes all drawn features with stable IDs
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
const data = await map.getGeoJSON();
|
|
322
|
+
console.log(`${data.features.length} features on map`);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**[`loadGeoJSON(fc)`](src/components/LeafletDrawMapElement.ts:390)**: Promise<void>
|
|
326
|
+
|
|
327
|
+
- Clears existing data and loads new FeatureCollection
|
|
328
|
+
- Does NOT auto-fit the view (use [`fitBoundsToData()`](src/components/LeafletDrawMapElement.ts:428) separately)
|
|
329
|
+
- Triggers [`leaflet-draw:ingest`](src/types/events.ts:51) event before loading
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
await map.loadGeoJSON({
|
|
333
|
+
type: "FeatureCollection",
|
|
334
|
+
features: [
|
|
335
|
+
/* ... */
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**[`addFeatures(fc)`](src/components/LeafletDrawMapElement.ts:406)**: Promise<string[]>
|
|
341
|
+
|
|
342
|
+
- Adds features to existing map data (does not clear)
|
|
343
|
+
- Returns array of assigned stable feature IDs
|
|
344
|
+
- Triggers [`leaflet-draw:ingest`](src/types/events.ts:51) event before adding
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
const ids = await map.addFeatures(newFeatures);
|
|
348
|
+
console.log("Added features with IDs:", ids);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**[`clearLayers()`](src/components/LeafletDrawMapElement.ts:400)**: Promise<void>
|
|
352
|
+
|
|
353
|
+
- Removes all features from map and internal storage
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
await map.clearLayers();
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Individual Feature Operations
|
|
360
|
+
|
|
361
|
+
**[`updateFeature(id, feature)`](src/components/LeafletDrawMapElement.ts:416)**: Promise<void>
|
|
362
|
+
|
|
363
|
+
- Replaces an existing feature by ID
|
|
364
|
+
- Visual synchronization is progressively enhanced
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
await map.updateFeature(featureId, {
|
|
368
|
+
type: "Feature",
|
|
369
|
+
properties: { name: "Updated" },
|
|
370
|
+
geometry: {
|
|
371
|
+
/* ... */
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**[`removeFeature(id)`](src/components/LeafletDrawMapElement.ts:422)**: Promise<void>
|
|
377
|
+
|
|
378
|
+
- Removes a feature and its visual representation by ID
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
await map.removeFeature(featureId);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### View Control
|
|
385
|
+
|
|
386
|
+
**[`setView(lat, lng, zoom?)`](src/components/LeafletDrawMapElement.ts:448)**: Promise<void>
|
|
387
|
+
|
|
388
|
+
- Sets map center and optionally zoom level
|
|
389
|
+
- Updates element properties to maintain consistency
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
await map.setView(39.7392, -104.9903, 12);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**[`fitBoundsToData(padding?)`](src/components/LeafletDrawMapElement.ts:428)**: Promise<void>
|
|
396
|
+
|
|
397
|
+
- Automatically fits map view to show all current data
|
|
398
|
+
- Optional padding as ratio of bounds size (default: 0.05 = 5%)
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
await map.fitBoundsToData(0.1); // 10% padding
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**[`fitBounds(bounds, padding?)`](src/components/LeafletDrawMapElement.ts:436)**: Promise<void>
|
|
405
|
+
|
|
406
|
+
- Fits map view to specified bounds
|
|
407
|
+
- Bounds format: `[[south, west], [north, east]]`
|
|
408
|
+
- Optional padding ratio (default: 0.05)
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
await map.fitBounds(
|
|
412
|
+
[
|
|
413
|
+
[39.6, -105.1], // southwest
|
|
414
|
+
[39.8, -104.8], // northeast
|
|
415
|
+
],
|
|
416
|
+
0.05,
|
|
417
|
+
);
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Data Import Helpers
|
|
421
|
+
|
|
422
|
+
**[`loadGeoJSONFromUrl(url)`](src/components/LeafletDrawMapElement.ts:510)**: Promise<void>
|
|
423
|
+
|
|
424
|
+
- Fetches GeoJSON from URL and loads it
|
|
425
|
+
- Expects `application/json` content type
|
|
426
|
+
- Automatically fits view to loaded data
|
|
427
|
+
- Emits error events on fetch/parse failures
|
|
428
|
+
|
|
429
|
+
```javascript
|
|
430
|
+
await map.loadGeoJSONFromUrl("/api/geodata.json");
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**[`loadGeoJSONFromText(text)`](src/components/LeafletDrawMapElement.ts:533)**: Promise<void>
|
|
434
|
+
|
|
435
|
+
- Parses GeoJSON from text string and loads it
|
|
436
|
+
- Automatically fits view to loaded data
|
|
437
|
+
- Emits error events on parse failures
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
const text = await file.text();
|
|
441
|
+
await map.loadGeoJSONFromText(text);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**[`exportGeoJSON()`](src/components/LeafletDrawMapElement.ts:459)**: Promise<FeatureCollection>
|
|
445
|
+
|
|
446
|
+
- Exports current data and emits [`leaflet-draw:export`](src/types/events.ts:57) event
|
|
447
|
+
- Returns the FeatureCollection for convenience
|
|
448
|
+
- Useful for triggering export workflows
|
|
449
|
+
|
|
450
|
+
```javascript
|
|
451
|
+
const exported = await map.exportGeoJSON();
|
|
452
|
+
// Listen for the event to trigger download/save workflows
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### Advanced Features
|
|
456
|
+
|
|
457
|
+
**[`mergePolygons(options?)`](src/components/LeafletDrawMapElement.ts:475)**: Promise<string | null>
|
|
458
|
+
|
|
459
|
+
- Merges all visible polygon features into a single polygon
|
|
460
|
+
- Removes original polygons and creates new merged feature
|
|
461
|
+
- Returns ID of merged feature, or null if no polygons to merge
|
|
462
|
+
- Emits `leaflet-draw:merged` event with merge details
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
const mergedId = await map.mergePolygons({
|
|
466
|
+
properties: { name: "Merged Area", type: "combined" },
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**[`setMeasurementUnits(system)`](src/components/LeafletDrawMapElement.ts:504)**: Promise<void>
|
|
471
|
+
|
|
472
|
+
- Changes measurement system for ruler tool
|
|
473
|
+
- Options: `"metric"` (meters/kilometers) or `"imperial"` (feet/miles)
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
await map.setMeasurementUnits("imperial");
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Events
|
|
480
|
+
|
|
481
|
+
The component emits typed CustomEvents that can be listened to for real-time updates. All events include structured `detail` objects.
|
|
482
|
+
|
|
483
|
+
#### Core Lifecycle Events
|
|
484
|
+
|
|
485
|
+
**[`leaflet-draw:ready`](src/types/events.ts:7)**
|
|
486
|
+
|
|
487
|
+
- Fired when map is fully initialized and ready for interaction
|
|
488
|
+
- Detail: `{ bounds?: [[number, number], [number, number]] }`
|
|
489
|
+
|
|
490
|
+
```javascript
|
|
491
|
+
map.addEventListener("leaflet-draw:ready", (e) => {
|
|
492
|
+
console.log("Map ready!", e.detail.bounds);
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**[`leaflet-draw:error`](src/types/events.ts:42)**
|
|
497
|
+
|
|
498
|
+
- Fired when errors occur (fetch failures, parse errors, etc.)
|
|
499
|
+
- Detail: `{ message: string, cause?: unknown }`
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
map.addEventListener("leaflet-draw:error", (e) => {
|
|
503
|
+
console.error("Map error:", e.detail.message, e.detail.cause);
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### Drawing & Editing Events
|
|
508
|
+
|
|
509
|
+
**[`leaflet-draw:created`](src/types/events.ts:15)**
|
|
510
|
+
|
|
511
|
+
- Fired when user creates a new feature via drawing tools
|
|
512
|
+
- Detail: `{ id: string, layerType: string, geoJSON: Feature }`
|
|
513
|
+
- Layer types: `'polygon'`, `'polyline'`, `'rectangle'`, `'circle'`, `'marker'`
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
map.addEventListener('leaflet-draw:created', (e) => {
|
|
517
|
+
const { id, layerType, geoJSON } = e.detail;
|
|
518
|
+
console.log(`Created ${layerType} with ID: ${id}`);
|
|
519
|
+
|
|
520
|
+
// Save to your backend
|
|
521
|
+
await saveFeature(id, geoJSON);
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**[`leaflet-draw:edited`](src/types/events.ts:25)**
|
|
526
|
+
|
|
527
|
+
- Fired when user modifies existing features via edit tools
|
|
528
|
+
- Detail: `{ ids: string[], geoJSON: FeatureCollection }`
|
|
529
|
+
- Includes all edited feature IDs and current state
|
|
530
|
+
|
|
531
|
+
```javascript
|
|
532
|
+
map.addEventListener('leaflet-draw:edited', (e) => {
|
|
533
|
+
const { ids, geoJSON } = e.detail;
|
|
534
|
+
console.log(`Edited ${ids.length} features`);
|
|
535
|
+
|
|
536
|
+
// Sync changes to backend
|
|
537
|
+
for (const id of ids) {
|
|
538
|
+
const feature = geoJSON.features.find(f => f.id === id);
|
|
539
|
+
if (feature) await updateFeature(id, feature);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**[`leaflet-draw:deleted`](src/types/events.ts:34)**
|
|
545
|
+
|
|
546
|
+
- Fired when user deletes features via delete tools
|
|
547
|
+
- Detail: `{ ids: string[], geoJSON: FeatureCollection }`
|
|
548
|
+
- `geoJSON` contains remaining features (after deletion)
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
map.addEventListener('leaflet-draw:deleted', (e) => {
|
|
552
|
+
const { ids } = e.detail;
|
|
553
|
+
console.log(`Deleted features: ${ids.join(', ')}`);
|
|
554
|
+
|
|
555
|
+
// Remove from backend
|
|
556
|
+
for (const id of ids) {
|
|
557
|
+
await deleteFeature(id);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### Data Flow Events
|
|
563
|
+
|
|
564
|
+
**[`leaflet-draw:ingest`](src/types/events.ts:51)**
|
|
565
|
+
|
|
566
|
+
- Fired BEFORE data is loaded/added to the map
|
|
567
|
+
- Detail: `{ fc: FeatureCollection, mode: 'load' | 'add' }`
|
|
568
|
+
- Listeners can mutate `detail.fc` to transform incoming data
|
|
569
|
+
- Useful for data validation, filtering, or preprocessing
|
|
570
|
+
|
|
571
|
+
```javascript
|
|
572
|
+
map.addEventListener("leaflet-draw:ingest", (e) => {
|
|
573
|
+
const { fc, mode } = e.detail;
|
|
574
|
+
|
|
575
|
+
// Filter out invalid features
|
|
576
|
+
e.detail.fc.features = fc.features.filter(
|
|
577
|
+
(f) => f.geometry && f.geometry.coordinates.length > 0,
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
// Add default properties
|
|
581
|
+
e.detail.fc.features.forEach((f) => {
|
|
582
|
+
f.properties = {
|
|
583
|
+
...f.properties,
|
|
584
|
+
imported: true,
|
|
585
|
+
timestamp: Date.now(),
|
|
586
|
+
};
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**[`leaflet-draw:export`](src/types/events.ts:57)**
|
|
592
|
+
|
|
593
|
+
- Fired when [`exportGeoJSON()`](src/components/LeafletDrawMapElement.ts:459) method is called
|
|
594
|
+
- Detail: `{ geoJSON: FeatureCollection, featureCount: number }`
|
|
595
|
+
- Use for triggering download workflows
|
|
596
|
+
|
|
597
|
+
```javascript
|
|
598
|
+
map.addEventListener("leaflet-draw:export", (e) => {
|
|
599
|
+
const { geoJSON, featureCount } = e.detail;
|
|
600
|
+
|
|
601
|
+
// Create download link
|
|
602
|
+
const blob = new Blob([JSON.stringify(geoJSON, null, 2)], {
|
|
603
|
+
type: "application/geo+json",
|
|
604
|
+
});
|
|
605
|
+
const url = URL.createObjectURL(blob);
|
|
606
|
+
const a = document.createElement("a");
|
|
607
|
+
a.href = url;
|
|
608
|
+
a.download = `map-data-${new Date().toISOString()}.geojson`;
|
|
609
|
+
a.click();
|
|
610
|
+
});
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### Extended Tool Events
|
|
614
|
+
|
|
615
|
+
**`leaflet-draw:drawstart`** / **`leaflet-draw:drawstop`**
|
|
616
|
+
|
|
617
|
+
- Fired when drawing mode starts/stops
|
|
618
|
+
- Useful for UI state management
|
|
619
|
+
|
|
620
|
+
**`leaflet-draw:editstart`** / **`leaflet-draw:editstop`**
|
|
621
|
+
|
|
622
|
+
- Fired when edit mode starts/stops
|
|
623
|
+
|
|
624
|
+
**`leaflet-draw:merged`**
|
|
625
|
+
|
|
626
|
+
- Fired after successful polygon merge operation
|
|
627
|
+
- Detail includes merge statistics and result
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
// Show/hide UI elements based on draw state
|
|
631
|
+
map.addEventListener("leaflet-draw:drawstart", () => {
|
|
632
|
+
document.querySelector("#toolbar").classList.add("drawing-active");
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
map.addEventListener("leaflet-draw:drawstop", () => {
|
|
636
|
+
document.querySelector("#toolbar").classList.remove("drawing-active");
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Feature ID Management
|
|
641
|
+
|
|
642
|
+
The component guarantees stable, persistent feature IDs that survive editing operations:
|
|
643
|
+
|
|
644
|
+
1. **Explicit IDs**: If a feature has `feature.id` property, it's preserved
|
|
645
|
+
2. **Property IDs**: If no `feature.id` but has `properties.id`, that's used
|
|
646
|
+
3. **Generated IDs**: Otherwise, a UUID is generated and stored in `properties.id`
|
|
647
|
+
|
|
648
|
+
```javascript
|
|
649
|
+
// Features maintain their IDs through edit cycles
|
|
650
|
+
const data = await map.getGeoJSON();
|
|
651
|
+
data.features.forEach((f) => {
|
|
652
|
+
console.log(`Feature ID: ${f.id}, Properties ID: ${f.properties?.id}`);
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### TypeScript Support
|
|
657
|
+
|
|
658
|
+
Full TypeScript definitions are provided in [`src/types/public.ts`](src/types/public.ts) and [`src/types/events.ts`](src/types/events.ts):
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
import type {
|
|
662
|
+
LeafletDrawMapElementAPI,
|
|
663
|
+
MeasurementSystem,
|
|
664
|
+
} from "@florasync/leaflet-geokit";
|
|
665
|
+
import type {
|
|
666
|
+
CreatedEventDetail,
|
|
667
|
+
EditedEventDetail,
|
|
668
|
+
} from "@florasync/leaflet-geokit/events";
|
|
669
|
+
|
|
670
|
+
const map = document.querySelector(
|
|
671
|
+
"leaflet-geokit",
|
|
672
|
+
) as LeafletDrawMapElementAPI;
|
|
673
|
+
|
|
674
|
+
map.addEventListener(
|
|
675
|
+
"leaflet-draw:created",
|
|
676
|
+
(e: CustomEvent<CreatedEventDetail>) => {
|
|
677
|
+
const { id, layerType, geoJSON } = e.detail;
|
|
678
|
+
// Fully typed event handling
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
```
|
|
190
682
|
|
|
191
683
|
---
|
|
192
684
|
|
|
@@ -286,6 +778,98 @@ E. Framework integration (React/Preact/Vue/Svelte)
|
|
|
286
778
|
|
|
287
779
|
---
|
|
288
780
|
|
|
781
|
+
## Framework Support
|
|
782
|
+
|
|
783
|
+
This section is the canonical place to discover first-class framework integrations for this project.
|
|
784
|
+
|
|
785
|
+
Current framework support:
|
|
786
|
+
|
|
787
|
+
- Django: [docs/shims/django.md](docs/shims/django.md)
|
|
788
|
+
- Preact: [docs/shims/preact.md](docs/shims/preact.md)
|
|
789
|
+
- React: [docs/shims/react.md](docs/shims/react.md)
|
|
790
|
+
|
|
791
|
+
### Django
|
|
792
|
+
|
|
793
|
+
Use the Django shim when your source of truth is a form field (`<textarea>`) and you want map edits synchronized to submitted GeoJSON.
|
|
794
|
+
|
|
795
|
+
- Shim docs: [docs/shims/django.md](docs/shims/django.md)
|
|
796
|
+
- Entrypoint: [src/django/index.ts](src/django/index.ts)
|
|
797
|
+
|
|
798
|
+
Quick import path:
|
|
799
|
+
|
|
800
|
+
```ts
|
|
801
|
+
import { initDjangoGeokit } from "@florasync/leaflet-geokit/django";
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### Preact
|
|
805
|
+
|
|
806
|
+
Use the Preact wrappers when you want component-level integration in Preact apps.
|
|
807
|
+
|
|
808
|
+
- Runtime dependency model: Preact is consumer-provided (peer dependency), so wrapper bundles stay thin.
|
|
809
|
+
|
|
810
|
+
- Shim docs: [docs/shims/preact.md](docs/shims/preact.md)
|
|
811
|
+
- Entrypoints: [src/preact/index.tsx](src/preact/index.tsx), [src/preact-bundled/index.tsx](src/preact-bundled/index.tsx)
|
|
812
|
+
|
|
813
|
+
### Preact wrapper (additive Leaflet mode)
|
|
814
|
+
|
|
815
|
+
If your Preact app already loads Leaflet + Leaflet.draw, use the Preact shim:
|
|
816
|
+
|
|
817
|
+
```ts
|
|
818
|
+
import { PreactLeafletGeoKit } from "@florasync/leaflet-geokit/preact";
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
It mounts `<leaflet-geokit>` in additive mode by default (`use-external-leaflet` + `skip-leaflet-styles`) and syncs GeoJSON via callbacks.
|
|
822
|
+
|
|
823
|
+
```tsx
|
|
824
|
+
<PreactLeafletGeoKit
|
|
825
|
+
style={{ width: "100%", height: "420px" }}
|
|
826
|
+
attributes={{
|
|
827
|
+
latitude: 39.7392,
|
|
828
|
+
longitude: -104.9903,
|
|
829
|
+
zoom: 11,
|
|
830
|
+
"draw-polygon": true,
|
|
831
|
+
"edit-features": true,
|
|
832
|
+
}}
|
|
833
|
+
onChangeText={(text) => {
|
|
834
|
+
// Persist serialized FeatureCollection
|
|
835
|
+
console.log(text);
|
|
836
|
+
}}
|
|
837
|
+
/>
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
For apps that do **not** preload Leaflet/Leaflet.draw, use the bundled Preact entrypoint:
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
import { PreactLeafletGeoKit } from "@florasync/leaflet-geokit/preact-bundled";
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
See full Preact shim docs: [docs/shims/preact.md](docs/shims/preact.md)
|
|
847
|
+
|
|
848
|
+
### React
|
|
849
|
+
|
|
850
|
+
Use the React wrappers when you want component-level integration in React apps.
|
|
851
|
+
|
|
852
|
+
- Runtime dependency model: React/ReactDOM are consumer-provided (peer dependencies), so wrapper bundles stay thin.
|
|
853
|
+
|
|
854
|
+
- Shim docs: [docs/shims/react.md](docs/shims/react.md)
|
|
855
|
+
- Entrypoints: [src/react/index.tsx](src/react/index.tsx), [src/react-bundled/index.tsx](src/react-bundled/index.tsx)
|
|
856
|
+
|
|
857
|
+
### React wrapper (additive Leaflet mode)
|
|
858
|
+
|
|
859
|
+
If your React app already loads Leaflet + Leaflet.draw, use the React shim:
|
|
860
|
+
|
|
861
|
+
```ts
|
|
862
|
+
import { ReactLeafletGeoKit } from "@florasync/leaflet-geokit/react";
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
For apps that do **not** preload Leaflet/Leaflet.draw, use the bundled React entrypoint:
|
|
866
|
+
|
|
867
|
+
```ts
|
|
868
|
+
import { ReactLeafletGeoKit } from "@florasync/leaflet-geokit/react-bundled";
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
See full React shim docs: [docs/shims/react.md](docs/shims/react.md)
|
|
872
|
+
|
|
289
873
|
## Recipes
|
|
290
874
|
|
|
291
875
|
1. Persist to your API
|
|
@@ -325,6 +909,29 @@ el.addEventListener("leaflet-draw:edited", async (e) => {
|
|
|
325
909
|
- At runtime, set el.logLevel = 'info' to reduce chatter.
|
|
326
910
|
- If you need a different sink, wrap the element logic in your app and forward to your own logger (see [src/utils/logger.ts](src/utils/logger.ts)).
|
|
327
911
|
|
|
912
|
+
6. Runtime theming (CSS injection)
|
|
913
|
+
|
|
914
|
+
- Provide a theme stylesheet URL via the theme-url attribute.
|
|
915
|
+
- Provide inline CSS overrides via the themeCss property.
|
|
916
|
+
- Cascade order is: built-in Leaflet styles → theme-url → themeCss.
|
|
917
|
+
- Updates are dynamic; changing theme-url or themeCss updates the Shadow DOM styles in place.
|
|
918
|
+
|
|
919
|
+
```html
|
|
920
|
+
<leaflet-geokit
|
|
921
|
+
theme-url="/themes/geokit-brand.css"
|
|
922
|
+
draw-polygon
|
|
923
|
+
edit-features
|
|
924
|
+
></leaflet-geokit>
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
```js
|
|
928
|
+
const el = document.querySelector("leaflet-geokit");
|
|
929
|
+
el.themeCss = `
|
|
930
|
+
.leaflet-container { font-family: "Inter", sans-serif; }
|
|
931
|
+
.leaflet-draw-toolbar a { border-radius: 8px; }
|
|
932
|
+
`;
|
|
933
|
+
```
|
|
934
|
+
|
|
328
935
|
---
|
|
329
936
|
|
|
330
937
|
## Logging and diagnostics
|