@florasync/leaflet-geokit 0.2.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 ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 FloraSync
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,400 @@
1
+ # @florasync/leaflet-geokit
2
+
3
+ A framework-agnostic, TypeScript-first Web Component that wraps Leaflet and Leaflet.draw to provide a beautiful, stateful, embeddable GeoJSON editor. Ships as a Vite library (ESM + UMD), bundles Leaflet/Draw assets, injects CSS into Shadow DOM, exposes a clean attributes/events/methods API, and includes verbose, structured logging for first-class diagnostics.
4
+
5
+ Primary sources
6
+
7
+ - Element host (custom element): [src/components/LeafletDrawMapElement.ts](src/components/LeafletDrawMapElement.ts)
8
+ - Controller (Leaflet + Draw lifecycle): [src/lib/MapController.ts](src/lib/MapController.ts)
9
+ - Feature store (ID-centric CRUD): [src/lib/FeatureStore.ts](src/lib/FeatureStore.ts)
10
+ - Assets and CSS bridge: [src/lib/leaflet-assets.ts](src/lib/leaflet-assets.ts)
11
+ - Public API and event types: [src/types/public.ts](src/types/public.ts), [src/types/events.ts](src/types/events.ts)
12
+ - Logger utility: [src/utils/logger.ts](src/utils/logger.ts)
13
+ - GeoJSON helpers: [src/utils/geojson.ts](src/utils/geojson.ts)
14
+
15
+ Documentation quick-links
16
+
17
+ - Architecture overview: [ARCHITECTURE.md](ARCHITECTURE.md)
18
+ - Dev harness (example): [index.html](index.html)
19
+ - Unit tests: [tests/element.spec.ts](tests/element.spec.ts)
20
+ - Vitest config: [vitest.config.ts](vitest.config.ts)
21
+ - Vite build config: [vite.config.ts](vite.config.ts)
22
+
23
+ ---
24
+
25
+ ## Contents
26
+
27
+ - What you get
28
+ - Install
29
+ - Build and compilation
30
+ - Runtime and architecture overview
31
+ - Public API (attributes, properties, methods, events)
32
+ - Usage examples (HTML, ESM, framework integration, recipes)
33
+ - Logging, diagnostics, and troubleshooting
34
+ - Performance, accessibility, and SSR notes
35
+ - Roadmap and versioning
36
+
37
+ ---
38
+
39
+ ## What you get
40
+
41
+ - A self-contained custom element, <leaflet-geokit>, that:
42
+ - Renders a Leaflet map inside Shadow DOM
43
+ - Configures Leaflet.draw tools via boolean attributes
44
+ - Injects Leaflet and Leaflet.draw CSS automatically
45
+ - Emits typed CustomEvents with id-aware payloads
46
+ - Offers an imperative API to import/export GeoJSON and control the view
47
+ - Exposes structured verbose logging with adjustable levels
48
+
49
+ - A design that separates:
50
+ - Web component host logic from Leaflet orchestration (controller)
51
+ - Controller logic from id-centric data storage (feature store)
52
+
53
+ - Strong TypeScript types for the public API (for consumers using TS)
54
+
55
+ ---
56
+
57
+ ## Install
58
+
59
+ Inside this package directory:
60
+
61
+ - npm install
62
+ - npm run dev — starts Vite dev server
63
+ - Open http://localhost:5173 in the browser (or whatever port VITE is using
64
+
65
+ For consumption from another app:
66
+
67
+ - Add this package as a dependency
68
+ - `npm install @florasync/leaflet-geokit`
69
+ - `import "@florasync/leaflet-geokit";` and ensure the element is sized via CSS (it does not self-size)
70
+
71
+ ---
72
+
73
+ ## Build and compilation
74
+
75
+ Tooling
76
+
77
+ - Build: Vite (library mode), target: ES2019
78
+ - Outputs:
79
+ - dist/leaflet-geokit.es.js (ESM)
80
+ - dist/leaflet-geokit.umd.js (UMD)
81
+ - dist/types/\*\* (TypeScript declaration files)
82
+ - Bundles Leaflet and Leaflet.draw by default
83
+ - CSS/Assets handled by Vite and injected into Shadow DOM at runtime
84
+
85
+ Scripts (see [package.json](package.json))
86
+
87
+ - npm run dev — Vite dev server
88
+ - npm run build — type declarations + Vite build
89
+ - npm run test:unit — Vitest (happy-dom)
90
+ - npm run typecheck — TypeScript noEmit
91
+ - npm run lint — ESLint (strict TS rules)
92
+ - npm run format — Prettier write
93
+ - npm run test:e2e — Playwright (currently a minimal smoke test under e2e/)
94
+
95
+ Browser support
96
+
97
+ - ES2019, evergreen browsers (Chromium, Firefox, Safari modern)
98
+
99
+ ---
100
+
101
+ ## Runtime and architecture overview
102
+
103
+ High-level components:
104
+
105
+ - [src/components/LeafletDrawMapElement.ts](src/components/LeafletDrawMapElement.ts)
106
+ - Manages Shadow DOM container and attributes/properties
107
+ - Instantiates the controller; delegates public methods
108
+ - Dispatches high-level CustomEvents
109
+
110
+ - [src/lib/MapController.ts](src/lib/MapController.ts)
111
+ - Creates Leaflet map and tile layer
112
+ - Manages L.FeatureGroup for drawn items
113
+ - Configures Leaflet.draw tools from attributes
114
+ - Bridges Leaflet.draw events to component events with typed payloads
115
+ - Provides procedural methods: setView, fitBoundsToData, etc.
116
+ - Persists features in the FeatureStore
117
+
118
+ - [src/lib/FeatureStore.ts](src/lib/FeatureStore.ts)
119
+ - Guarantees stable feature ids (feature.id → properties.id → generated uuid)
120
+ - CRUD operations and conversion to FeatureCollection
121
+ - Computes bounds for fit-to-data
122
+
123
+ CSS and assets:
124
+
125
+ - [src/lib/leaflet-assets.ts](src/lib/leaflet-assets.ts) injects Leaflet + Leaflet.draw CSS into ShadowRoot and wires default marker icons via bundled asset URLs.
126
+
127
+ ---
128
+
129
+ ## Public API
130
+
131
+ Attributes (string/boolean)
132
+
133
+ - Map configuration
134
+ - latitude (string number): initial map latitude; default 0
135
+ - longitude (string number): initial map longitude; default 0
136
+ - zoom (string number): initial zoom; default 2
137
+ - min-zoom (string number, optional)
138
+ - max-zoom (string number, optional)
139
+ - tile-url (string): tile URL template (default OSM)
140
+ - tile-attribution (string, optional): attribution text
141
+ - prefer-canvas (boolean): use Canvas rendering instead of SVG for better performance with large datasets; default true
142
+ - Controls (boolean; presence = enabled)
143
+ - draw-polygon, draw-polyline, draw-rectangle, draw-circle, draw-layer-cake, draw-marker
144
+ - edit-features, delete-features
145
+ - Behavior
146
+ - read-only (boolean): disables all drawing/editing/removing
147
+ - log-level (string): trace | debug | info | warn | error | silent (default debug)
148
+ - dev-overlay (boolean): reserved for a runtime overlay (future)
149
+
150
+ Properties (runtime)
151
+
152
+ - latitude: number
153
+ - longitude: number
154
+ - zoom: number
155
+ - minZoom?: number
156
+ - maxZoom?: number
157
+ - tileUrl: string
158
+ - tileAttribution?: string
159
+ - readOnly: boolean
160
+ - logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
161
+ - devOverlay: boolean
162
+ - preferCanvas: boolean
163
+
164
+ Methods (Promise-based, invoked on the element instance)
165
+
166
+ - getGeoJSON(): returns FeatureCollection of current data
167
+ - loadGeoJSON(fc): clears + loads FeatureCollection; does not auto-fit
168
+ - clearLayers(): clears map layers and store
169
+ - addFeatures(fc): adds features; returns array of assigned ids
170
+ - updateFeature(id, feature): replaces a feature in the store (visual sync is progressively enhanced)
171
+ - removeFeature(id): removes a feature (and layers with matching id)
172
+ - fitBoundsToData(padding?): fits view to data bounds (default padding ~0.05)
173
+ - fitBounds(bounds, padding?): fits view to provided [[south, west], [north, east]] bounds with optional padding ratio
174
+ - setView(lat, lng, zoom?): sets map view
175
+ - loadGeoJSONFromUrl(url): fetches a URL (application/json) and loads; auto-fits
176
+ - loadGeoJSONFromText(text): parses JSON text and loads; auto-fits
177
+ - exportGeoJSON(): emits 'leaflet-draw:export' with current FeatureCollection and returns it
178
+
179
+ Events (CustomEvent with detail)
180
+
181
+ - leaflet-draw:ready — { bounds?: [[south, west], [north, east]] }
182
+ - leaflet-draw:created — { id: string, layerType: 'polygon'|'polyline'|'rectangle'|'circle'|'marker', geoJSON: Feature }
183
+ - leaflet-draw:edited — { ids: string[], geoJSON: FeatureCollection }
184
+ - leaflet-draw:deleted — { ids: string[], geoJSON: FeatureCollection }
185
+ - leaflet-draw:error — { message: string, cause?: unknown }
186
+ - leaflet-draw:export — { geoJSON: FeatureCollection, featureCount: number }
187
+ - leaflet-draw:ingest — { fc: FeatureCollection, mode: 'load'|'add' } (listener may mutate detail.fc to transform input)
188
+
189
+ Event payload types live in [src/types/events.ts](src/types/events.ts). Public API types live in [src/types/public.ts](src/types/public.ts).
190
+
191
+ ---
192
+
193
+ ## Usage examples
194
+
195
+ A. Basic HTML, served by Vite (development)
196
+
197
+ ```html
198
+ <style>
199
+ leaflet-geokit {
200
+ display: block;
201
+ width: 100%;
202
+ height: 500px;
203
+ }
204
+ </style>
205
+ <script type="module" src="/src/index.ts"></script>
206
+
207
+ <leaflet-geokit
208
+ latitude="39.7392"
209
+ longitude="-104.9903"
210
+ zoom="11"
211
+ tile-url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
212
+ draw-polygon
213
+ draw-polyline
214
+ draw-rectangle
215
+ draw-circle
216
+ draw-layer-cake
217
+ draw-marker
218
+ edit-features
219
+ delete-features
220
+ log-level="debug"
221
+ ></leaflet-geokit>
222
+
223
+ <script type="module">
224
+ const el = document.querySelector("leaflet-geokit");
225
+
226
+ el.addEventListener("leaflet-draw:ready", (e) =>
227
+ console.log("READY", e.detail),
228
+ );
229
+ el.addEventListener("leaflet-draw:created", (e) =>
230
+ console.log("CREATED", e.detail),
231
+ );
232
+ el.addEventListener("leaflet-draw:edited", (e) =>
233
+ console.log("EDITED", e.detail),
234
+ );
235
+ el.addEventListener("leaflet-draw:deleted", (e) =>
236
+ console.log("DELETED", e.detail),
237
+ );
238
+ </script>
239
+ ```
240
+
241
+ B. ESM consumption from another app (node_modules)
242
+
243
+ ```js
244
+ // main.ts
245
+ import "@florasync/leaflet-geokit";
246
+
247
+ // later in DOM:
248
+ const el = document.querySelector("leaflet-geokit");
249
+ await el.loadGeoJSONFromUrl("/data/feature-collection.json");
250
+ ```
251
+
252
+ C. Load from text (e.g., user paste or file input)
253
+
254
+ ```js
255
+ const text = await file.text();
256
+ await el.loadGeoJSONFromText(text);
257
+ ```
258
+
259
+ D. Programmatic CRUD with ids
260
+
261
+ ```js
262
+ const ids = await el.addFeatures({
263
+ type: "FeatureCollection",
264
+ features: [
265
+ {
266
+ type: "Feature",
267
+ properties: { name: "A" },
268
+ geometry: { type: "Point", coordinates: [-105, 39.73] },
269
+ },
270
+ ],
271
+ });
272
+ await el.updateFeature(ids[0], {
273
+ type: "Feature",
274
+ properties: { name: "A (edited)" },
275
+ geometry: { type: "Point", coordinates: [-105.01, 39.735] },
276
+ });
277
+ await el.removeFeature(ids[0]);
278
+ ```
279
+
280
+ E. Framework integration (React/Preact/Vue/Svelte)
281
+
282
+ - Use the custom element as a regular JSX/HTML element.
283
+ - Ensure typescript recognizes custom elements (tsconfig compilerOptions.lib includes DOM).
284
+ - Manage data in your state and pass event handlers via addEventListener in useEffect/onMount.
285
+ - Remember to set CSS height on the element.
286
+
287
+ ---
288
+
289
+ ## Recipes
290
+
291
+ 1. Persist to your API
292
+
293
+ ```js
294
+ el.addEventListener("leaflet-draw:edited", async (e) => {
295
+ await fetch("/api/shapes", {
296
+ method: "PUT",
297
+ headers: { "content-type": "application/json" },
298
+ body: JSON.stringify(e.detail.geoJSON),
299
+ });
300
+ });
301
+ ```
302
+
303
+ 2. Read-only review mode
304
+
305
+ ```html
306
+ <leaflet-geokit read-only></leaflet-geokit>
307
+ ```
308
+
309
+ 3. Alternate tile provider with attribution
310
+
311
+ ```html
312
+ <leaflet-geokit
313
+ tile-url="https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
314
+ tile-attribution="&copy; OSM contributors, Humanitarian style"
315
+ />
316
+ ```
317
+
318
+ 4. Fit view after load
319
+
320
+ - loadGeoJSONFromUrl and loadGeoJSONFromText auto-fit to the loaded data.
321
+ - loadGeoJSON does not auto-fit; call el.fitBoundsToData(0.1) after load if desired.
322
+
323
+ 5. Custom logger injection (advanced)
324
+
325
+ - At runtime, set el.logLevel = 'info' to reduce chatter.
326
+ - 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
+
328
+ ---
329
+
330
+ ## Logging and diagnostics
331
+
332
+ Logger namespaces:
333
+
334
+ - component:leaflet-geokit — element lifecycle, attribute changes, public methods
335
+ - component:leaflet-geokit:controller — controller init, draw events, CRUD, timings
336
+ - component:leaflet-geokit:controller:store — feature add/update/remove, bounds, ids
337
+
338
+ Control verbosity:
339
+
340
+ - Attribute log-level="debug" (default 'debug')
341
+ - el.logLevel at runtime
342
+
343
+ Troubleshooting checklist:
344
+
345
+ - Blank/empty map
346
+ - Ensure the element has a fixed height (CSS); 0-height containers won’t render.
347
+ - For hidden tabs, call setView or fitBoundsToData once the tab becomes visible.
348
+ - Draw tools missing
349
+ - Confirm boolean attributes are present (e.g., draw-polygon). read-only disables tools entirely.
350
+ - Events not received
351
+ - Add listeners directly on the element instance (e.target is the custom element). Verify event names.
352
+ - IDs absent or not stable
353
+ - Persist feature.id (or properties.id) in your backend; the store uses and preserves them.
354
+
355
+ ---
356
+
357
+ ## Performance, accessibility, SSR
358
+
359
+ Performance
360
+
361
+ - **Canvas Rendering (Default)**: The component uses Canvas rendering by default (prefer-canvas="true"), which provides significantly better performance than SVG when displaying large numbers of features or complex polygons. Canvas uses a single canvas element instead of individual DOM elements for each feature, reducing DOM overhead and improving rendering speed.
362
+ - To switch to SVG rendering (e.g., for better print quality or specific styling needs), remove the prefer-canvas attribute or set it explicitly: `<leaflet-geokit prefer-canvas="false">`.
363
+ - L.geoJSON is adequate for small/medium collections. For very large datasets (thousands of features), consider server-side tiling or clustering (not included).
364
+ - fitBoundsToData uses padding to reduce cramped framing; tune via method arg.
365
+
366
+ Accessibility
367
+
368
+ - Keyboard navigation and zoom are Leaflet-provided. Consider documenting shortcuts in your app.
369
+ - Host element applies modern focus/shape defaults; restyle as desired.
370
+
371
+ SSR
372
+
373
+ - This is a browser-only custom element. If importing in SSR, gate the import to client-side only.
374
+
375
+ ---
376
+
377
+ ## Roadmap and versioning
378
+
379
+ Planned enhancements
380
+
381
+ - Dev overlay (opt-in) to visualize state, counts, and last event payloads
382
+ - Geometry-level layer sync for updateFeature without re-add
383
+ - Playwright e2e and CI workflows
384
+ - Advanced import providers (files, streams) and output format adapters
385
+ - Theming hooks for overlay UI
386
+
387
+ Versioning and releases
388
+
389
+ - Types shipped in dist/types
390
+ - Keep a Changelog in CHANGELOG.md (to be populated during releases)
391
+
392
+ ---
393
+
394
+ ## License
395
+
396
+ ## MIT
397
+
398
+ ## Contributing
399
+
400
+ Please see CONTRIBUTING.md for environment setup, testing, and our Good Vibes Policy. PRs with clear rationale, small focused changes, and tests are very welcome.