@zwishing/emap 0.2.0 → 0.3.1

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +226 -1
  2. package/FEATURES.md +455 -0
  3. package/README.md +414 -209
  4. package/dist/core/event-map.d.ts +67 -0
  5. package/dist/core/feature-event-dispatcher.d.ts +70 -0
  6. package/dist/core/handler-manager.d.ts +49 -0
  7. package/dist/core/handler.d.ts +48 -0
  8. package/dist/core/handlers/box-select.d.ts +53 -0
  9. package/dist/core/handlers/click-select.d.ts +31 -0
  10. package/dist/core/handlers/drag-pan.d.ts +35 -0
  11. package/dist/core/handlers/draw-feature.d.ts +76 -0
  12. package/dist/core/handlers/lasso-select.d.ts +57 -0
  13. package/dist/core/handlers/scroll-zoom.d.ts +24 -0
  14. package/dist/core/handlers/select-geometry.d.ts +24 -0
  15. package/dist/core/handlers/select-mode.d.ts +14 -0
  16. package/dist/core/handlers/transform-feature.d.ts +47 -0
  17. package/dist/core/handlers/vertex-edit.d.ts +98 -0
  18. package/dist/core/pointer-event-dispatcher.d.ts +40 -0
  19. package/dist/core/tween.d.ts +1 -0
  20. package/dist/emap.css +42 -8
  21. package/dist/emap.js +2 -2
  22. package/dist/emap.mjs +1 -1
  23. package/dist/geo/camera.d.ts +100 -0
  24. package/dist/geo/projection.d.ts +8 -1
  25. package/dist/geo/viewport.d.ts +18 -0
  26. package/dist/index.d.ts +26 -2
  27. package/dist/map/edit-state-store.d.ts +1 -1
  28. package/dist/map/map.d.ts +89 -2
  29. package/dist/map/selection.d.ts +5 -2
  30. package/dist/renderer/edit-overlay-renderer.d.ts +2 -1
  31. package/dist/source/source.d.ts +2 -2
  32. package/dist/source/topology-source.d.ts +7 -2
  33. package/dist/ui/box-select-control.d.ts +19 -37
  34. package/dist/ui/draw-feature-control.d.ts +6 -71
  35. package/dist/ui/lasso-select-control.d.ts +14 -61
  36. package/dist/ui/status-control.d.ts +2 -2
  37. package/dist/ui/vertex-edit-control.d.ts +5 -100
  38. package/package.json +5 -1
  39. package/dist/core/drag-pan-handler.d.ts +0 -28
package/CHANGELOG.md CHANGED
@@ -7,6 +7,230 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.1] - 2026-05-21
11
+
12
+ ### Changed
13
+
14
+ - `boxSelect` now behaves like an active selection tool by default: plain
15
+ left-drag starts a rectangular selection and replaces the previous selection,
16
+ while `Shift`+drag adds to the existing selection and `Shift`+`Alt`+drag
17
+ toggles matching features.
18
+ - `transform` edit mode keeps map panning available outside already-selected
19
+ features; dragging selected features still starts a transform session.
20
+ - When pan or translate-transform mode is active and the pointer hovers an
21
+ already-selected feature, the canvas cursor now switches to `move`; active
22
+ map dragging still uses `grabbing`.
23
+
24
+ ## [0.3.0] - 2026-05-21
25
+
26
+ ### Added
27
+
28
+ - DX Phase 1 (toward 0.3.0): `Handler` interface + `HandlerManager`; the pan
29
+ and wheel interactions are now named handlers `map.dragPan` /
30
+ `map.scrollZoom` with `enable()/disable()/isEnabled()/setOptions()`.
31
+ Enables selective interaction toggling (e.g. disable wheel, keep pan)
32
+ without reaching into private internals. No behavior change for the
33
+ default path (`interactive` unset/true): the underlying pan/wheel
34
+ primitives are unchanged and all existing tests pass byte-identically.
35
+ One intentional fix: `MapOptions.interactive: false` was previously a
36
+ declared-but-unwired no-op — it is now functional and disables pan+zoom
37
+ as the option always implied. See
38
+ `docs/superpowers/specs/2026-05-17-emap-maplibre-dx-design.md`.
39
+ - DX Phase 2a (toward 0.3.0): `HandlerManager` is now the single
40
+ pointer-event arbiter (`attach`/`detach`, normalized `NormalizedPointerEvent`,
41
+ priority-ordered dispatch with exclusive-gesture locking). `dragPan` pans
42
+ via `handlePointer` and owns no DOM listeners; the old internal pan
43
+ primitive (`src/core/drag-pan-handler.ts`) is removed. No pan behavior
44
+ change; this is the seam P2b's selection handlers slot into.
45
+ - DX Phase 2b (toward 0.3.0): three opt-in named selection handlers —
46
+ `map.clickSelect`, `map.boxSelect`, `map.lassoSelect` (each a `Handler`
47
+ with `enable/disable/isEnabled/setOptions/getOptions`). Driven by the
48
+ Phase-2a pointer arbiter; `box`/`lasso` sit above `dragPan` so
49
+ `shift`+drag selects (when enabled) instead of panning, with no
50
+ `addControl` needed. Shared pure `resolveMode` (configurable
51
+ modifier→`SelectMode` table) and a shared `select-geometry` module
52
+ replace the duplicated private `_modeFromEvent`. `boxSelect` and
53
+ `lassoSelect` are mutually exclusive (`enable()` throws if the other is
54
+ on). Disabled by default → `new Emap()` behavior is unchanged; the
55
+ legacy `BoxSelectControl`/`LassoSelectControl` keep working unchanged
56
+ (thin-shell rewrite is Phase 2c).
57
+ - DX Phase 2c (toward 0.3.0): `BoxSelectControl` / `LassoSelectControl`
58
+ are now button-only thin shells over the P2b `map.boxSelect` /
59
+ `map.lassoSelect` handlers (the ~490 lines of duplicated
60
+ listener/overlay/hit-test/select logic deleted) — public class,
61
+ constructor options, 0×0 anchor DOM, selection behaviour, and the
62
+ dashed-lasso visual are byte-identical (the handler now honours
63
+ `strokeDasharray`, default `'4 3'`). Adding both controls now surfaces
64
+ the handlers' mutual-exclusivity error instead of silently conflicting.
65
+ Duplicated types consolidated to one declaration each with re-export
66
+ shims (every public import path unchanged): `SelectMode` →
67
+ `core/handlers/select-mode`, `BoxSelectStyle` →
68
+ `core/handlers/box-select`, `LassoSelectStyle`/`LassoStyle` →
69
+ `core/handlers/lasso-select` (`LassoStyle` widened additively to include
70
+ the optional `strokeDasharray`). No arbiter/dispatch change.
71
+ - DX Phase 3 (toward 0.3.0): delegated feature events —
72
+ `map.on('feature:click'|'feature:hover'|'feature:enter'|'feature:leave',
73
+ { source?, layers? }?, cb)`. Fed by the Phase-2a pointer arbiter, hit-test
74
+ reuses the existing `FeatureQuery` (no new path), decoupled from
75
+ selection. Zero cost when no `feature:*` listener is registered (the
76
+ pointer sink is installed lazily). Canonical highlight recipe:
77
+ `map.on('feature:enter', {}, e => map.setHighlightedFeatures([e.ref]));
78
+ map.on('feature:leave', {}, () => map.clearHighlightedFeatures());` —
79
+ replaces the manual mousemove-poll + highlight-sync boilerplate.
80
+ - DX Phase 4 (toward 0.3.0): opened the emap design-token namespace —
81
+ `controls.css` now declares 9 `--emap-*` custom properties on `:root`
82
+ (`accent`, `accent-soft`, `surface-bg`, `surface-border`,
83
+ `surface-shadow`, `radius`, `hover-bg`, `danger`, `font`) with the
84
+ current literal values as defaults. The vertex-edit right-click menu
85
+ ("删除节点") migrated off inline styles + JS `mouseenter`/`mouseleave`
86
+ hover toggle onto class-based rules (`.emap-ctx-menu` /
87
+ `.emap-ctx-menu-item` with `:hover`) consuming the tokens. Default
88
+ rendering is byte-identical; themers override `--emap-*` on `:root` (or
89
+ a parent) to restyle. Existing controls' literal values are unchanged
90
+ — namespace opened but not retro-applied (follow-up phases may opt in).
91
+ - DX Phase 5 (toward 0.3.0): vertex editing is now the arbiter-driven
92
+ `map.vertexEdit` handler (`enable/disable/isEnabled/setOptions/
93
+ getOptions`), consistent with the other handlers — usable without
94
+ `addControl`. The arbiter gained a `contextmenu` event kind (right-click
95
+ vertex delete). `VertexEditControl` is now a button-only thin shell over
96
+ `map.vertexEdit`; its public class, constructor options, button DOM,
97
+ `editmodechange`/edit-mode behavior, undo commands, and the
98
+ `vertex_moved` event are byte-identical. Disabled by default →
99
+ `new Emap()` behavior unchanged. (P4 will migrate the context-menu
100
+ inline styles to CSS tokens.)
101
+ - DX Phase 6 (toward 0.3.0): feature drawing is now the arbiter-driven
102
+ `map.drawFeature` handler (`enable/disable/isEnabled/setOptions/
103
+ getOptions`) — usable without `addControl`. The arbiter gained a
104
+ `dblclick` event kind (finish polyline/polygon); `Escape`-to-cancel is a
105
+ handler-owned key listener. `enable()` throws if `source`/`type` are
106
+ unset. `DrawFeatureControl` is now a button-only thin shell over
107
+ `map.drawFeature`; its public class, constructor options (incl. `icon`),
108
+ button DOM, `feature_created` event, `FeatureCreateCommand`, and the
109
+ cross-mode auto-deactivate are byte-identical. Disabled (and
110
+ unconfigured) by default → `new Emap()` behavior unchanged.
111
+ - DX Phase 7 (toward 0.3.0): feature transform is now the arbiter-driven
112
+ modal `map.transformFeature` handler (`setOptions({ mode:
113
+ 'translate'|'rotate'|'scale' })`, `enable/disable/isEnabled/getOptions`).
114
+ Selection is delegated to `clickSelect` (click = replace, shift+click =
115
+ add, click on empty space = clear); `transformFeature` owns no
116
+ selection logic — it press-drags an already-selected feature:
117
+ pointerdown over a selected feature opens the matching `editSession`,
118
+ drag streams the delta, pointerup commits one undo entry, `Escape`
119
+ cancels. `EditMode` gained `'transform'` (enters the modal pan-disable +
120
+ `editmodechange` like vertex/draw; cross-deactivates with
121
+ vertexEdit/drawFeature). The headless `map.editSession` API is unchanged
122
+ — this only absorbs the pointer→session boilerplate. Disabled by default
123
+ → `new Emap()` behavior unchanged. The `feature-translate` /
124
+ `feature-rotate-scale` examples are rewritten to the one-line recipe.
125
+ - DX Phase 8 (toward 0.3.0): delegated `map.on('mousemove', cb)` event
126
+ (payload `{ point, mapCoord, originalEvent }`), fed by the Phase-2a
127
+ pointer arbiter via the same sink mechanism as the Phase-3 `feature:*`
128
+ events — exported `PointerMoveEvent` type. `StatusControl` no longer
129
+ hand-wires a raw canvas `mousemove`; it subscribes the delegated event
130
+ (public class/options/DOM/coordinate-string unchanged). Zero cost when
131
+ no `mousemove`/`feature:*` listener is registered (the pointer sink is
132
+ installed lazily and shared). One intentional, documented behavior
133
+ change: the coordinate readout pauses during an exclusive pan/drag
134
+ gesture (consistent with `feature:hover`) and resumes on release.
135
+ Disabled-by-default cost model unchanged → `new Emap()` behavior
136
+ identical.
137
+ - DX Phase 9 (toward 0.3.0): MapLibre-parity camera API — `map.jumpTo`,
138
+ `setCenter`, `setZoom`, `easeTo`, `panTo`, `panBy`, `zoomTo`, `flyTo`
139
+ (van-Wijk parabolic), `fitBounds`, `stop` (all chainable, return
140
+ `this`), built on the existing `Tween`/`Viewport`. Fires a
141
+ `movestart`/`move`/`moveend` + `zoomstart`/`zoom`/`zoomend` lifecycle.
142
+ Camera state is `{ center (projected/data coords, = the Phase-8
143
+ `mapCoord` space), scale }`; `zoom` is an optional Web-Mercator
144
+ convenience (`Projection.getScale` inverse) — non-Web-Mercator CRSs
145
+ pass `{ scale }`. 2D only (no bearing/pitch). A new camera call (or a
146
+ user pan/wheel) interrupts a running animation; the interrupted move
147
+ still fires its `moveend`. `getCenter`/`getZoom`/`setExtent`/`reset`/
148
+ `zoomIn`/`zoomOut` are byte-identical; default `new Emap()` behaviour
149
+ unchanged (no animation runs until a camera method is called).
150
+ - DX Phase 10 (toward 0.3.0): typed `EmapEventMap` — `map.on` / `off` /
151
+ `once` now give
152
+ event-name autocomplete and payload inference for the camera lifecycle
153
+ (`movestart`/`move`/`moveend`/`zoomstart`/`zoom`/`zoomend`) and the
154
+ `data`/`error`/`selectionchange`/`historychange`/`editmodechange`/
155
+ `crschange`/`resize`/`validationfailed` events. `EmapEventMap` is an
156
+ augmentable `interface` (`declare module` to register custom events);
157
+ unknown event names still compile (escape hatch preserved). Compile-time
158
+ only — zero runtime change. Exported: `EmapEventMap`, `CameraEvent`.
159
+ - DX Phase 11 (toward 0.3.0): per-handler cursor — each `Handler` may
160
+ implement `getCursor(): string | null`; `HandlerManager` arbitrates one
161
+ effective cursor (active gesture > priority order > default) and writes
162
+ it to the map element via `refreshCursor()` / on every dispatch. Adds
163
+ the previously-absent `grab`/`grabbing` pan cursors and the draw
164
+ crosshair (the old `.emap-draw-active` CSS rule was dead); the ad-hoc
165
+ `.emap-edit-active`/`.emap-draw-active` CSS-class mechanism is removed
166
+ (single source of truth). No dispatch/priority behaviour change.
167
+
168
+ ### Changed
169
+
170
+ - **`LassoStyle` widened (additive, back-compat):** the public
171
+ `LassoStyle` type (`@zwishing/emap` index, from
172
+ `core/handlers/lasso-select`) gained an optional `strokeDasharray?:
173
+ string` field (default `'4 3'`). Every existing 3-field value remains
174
+ assignable; consumers passing an extra `strokeDasharray` to a lasso
175
+ handler/control now see the dashed visual previously only possible via
176
+ the `LassoSelectControl` surface.
177
+ - **Adding `BoxSelectControl` + `LassoSelectControl` together now
178
+ throws:** the P2b handlers are mutually exclusive (one of `enable()`
179
+ throws `cannot enable boxSelect: lassoSelect is enabled`). The legacy
180
+ controls silently coexisted with undefined behavior; the P2c thin
181
+ shells surface the conflict honestly. Install one, OR call
182
+ `map.boxSelect.disable()` before enabling the other (see the updated
183
+ `test/examples/lasso-select.html` for the canonical toggle recipe).
184
+ - Release metadata now targets `0.3.0`, matching the handler-centric public API
185
+ documented in README/FEATURES.
186
+
187
+ ### Fixed
188
+
189
+ - Made the bundle self-contained smoke test portable on Windows by resolving
190
+ local CLI shims through `cmd /c`, adding explicit `esbuild` dev dependency
191
+ coverage, and relaxing the GeoJSON bundle-load timeout for slower full-suite
192
+ runs.
193
+ - Completed the typed event surface: `EmapEventMap` now includes the delegated
194
+ `mousemove` and `feature:*` events, so facade authors can index the event map
195
+ for those 0.3.0 APIs instead of relying only on `map.on(...)` overloads.
196
+
197
+ ### Documentation
198
+
199
+ - Added third-party release guidance: static asset deployment, full custom
200
+ box-selection wiring without bundled controls, known limitations, API
201
+ stability boundaries, and a release checklist. The build now cleans `dist`
202
+ before production output so stale declaration files are not packed.
203
+ - Refreshed the project documentation for the current handler-centric public
204
+ API: rewrote `README.md` around third-party integration, headless handler
205
+ usage, custom UI patterns, feature events, editing, worker setup, theming,
206
+ and built-in controls; moved `docs/FEATURES.md` to the package root as
207
+ `FEATURES.md` and updated it as the canonical capability matrix. Removed
208
+ the mandatory OpenSpec workflow instructions from `AGENTS.md` and
209
+ `CLAUDE.md`, keeping them focused on current project architecture and
210
+ contribution rules.
211
+ - Documented the explicit 0.2.0 loading contract: `setData()` resolves only
212
+ after the dataset is populated and `data{reason:'load'}` has fired, and
213
+ `map.addSource()` is order-independent (back-fills an already-loaded
214
+ source). Added a troubleshooting note — a blank map with no error after
215
+ "upgrading to 0.2.0" is the *pre-0.2.0* fire-and-forget symptom and
216
+ indicates a stale install, not an emap defect (a reopened Friction #9
217
+ report was root-caused to this; the published 0.2.0 runtime was verified
218
+ to settle the promise after the data event).
219
+ - Modernized all 41 `test/examples/*.html` to the canonical awaitable
220
+ recipe (`await source.setData(...)` → `map.addSource(...)` →
221
+ `map.setExtent(...)` in a `try/catch`), replacing the older event-driven
222
+ `source.on('data'/'error')` load pattern. Examples now match the README
223
+ "Loading contract" verbatim, removing the example/doc divergence that
224
+ contributed to the reopened #9 misdiagnosis. Per-file status text,
225
+ post-load logic, and multi-file/Promise-wrapper variants preserved.
226
+
227
+ ### Tested
228
+
229
+ - Added `src/map/setdata-addsource-ordering.test.ts`: end-to-end regression
230
+ proving both `addSource`→`await setData` and `await setData`→`addSource`
231
+ populate the dataset before the promise resolves and schedule a render
232
+ (guards the loading contract against regression).
233
+
10
234
  ## [0.2.0] - 2026-05-17
11
235
 
12
236
  ### Changed
@@ -112,7 +336,8 @@ Initial public release.
112
336
  - Distributed control stylesheet as `dist/emap.css` (importable via
113
337
  `@zwishing/emap/style.css`).
114
338
 
115
- [Unreleased]: https://github.com/zwishing/emap/compare/v0.2.0...HEAD
339
+ [Unreleased]: https://github.com/zwishing/emap/compare/v0.3.0...HEAD
340
+ [0.3.0]: https://github.com/zwishing/emap/compare/v0.2.0...v0.3.0
116
341
  [0.2.0]: https://github.com/zwishing/emap/compare/v0.1.3...v0.2.0
117
342
  [0.1.3]: https://github.com/zwishing/emap/compare/v0.1.2...v0.1.3
118
343
  [0.1.2]: https://github.com/zwishing/emap/compare/v0.1.1...v0.1.2
package/FEATURES.md ADDED
@@ -0,0 +1,455 @@
1
+ # emap Feature Overview
2
+
3
+ This document lists the current public capabilities of `emap`. It complements
4
+ the quick-start focused [README.md](README.md).
5
+
6
+ ## 1. Runtime and Packaging
7
+
8
+ - Browser-only TypeScript library.
9
+ - Framework-agnostic: usable from vanilla HTML, React, Vue, Svelte, Angular, or
10
+ any other browser app.
11
+ - IIFE and ESM builds.
12
+ - Self-contained browser bundler path with Mapshaper supplied as package
13
+ sibling bundles.
14
+ - Optional worker bundle for off-thread dataset operations.
15
+ - Public CSS entry: `@zwishing/emap/style.css`.
16
+
17
+ ## 2. Public API Stability
18
+
19
+ Recommended stable integration surface:
20
+
21
+ - `Emap`
22
+ - `TopologySource`
23
+ - map source/layer/camera APIs
24
+ - named handlers exposed on `map.*`
25
+ - feature events and typed map events
26
+ - `map.ops.*`
27
+ - `map.features`, `map.validators`, `map.editSession`
28
+ - built-in controls
29
+ - documented types exported from the package root
30
+
31
+ Advanced but less stable surfaces:
32
+
33
+ - `HandlerManager`
34
+ - concrete handler classes
35
+ - `CanvasPainter` and `EditOverlayRenderer`
36
+ - `MapshaperAdapter` replacement hooks
37
+ - edit command classes
38
+ - raw Mapshaper topology types
39
+
40
+ These advanced exports are useful for custom integrations and tests, but they
41
+ may change faster than the main `Emap` and `TopologySource` API while the
42
+ project is still pre-production.
43
+
44
+ ## 3. Data Sources
45
+
46
+ Supported inputs:
47
+
48
+ | Input | Entry |
49
+ |---|---|
50
+ | GeoJSON | `TopologySource.fromUrl()`, `fromBytes()`, `setData()` |
51
+ | TopoJSON | `TopologySource.fromUrl()`, `fromBytes()`, `setData()` |
52
+ | ZIP Shapefile | `TopologySource.fromUrl()`, `fromBytes()`, `setData()` |
53
+ | Mapshaper snapshot | `map.loadSnapshot()` |
54
+
55
+ Source capabilities:
56
+
57
+ - Awaitable loading contract: `setData()` resolves after data is ready.
58
+ - Order-independent `map.addSource()`.
59
+ - CRS inference from GeoJSON defaults and Shapefile `.prj`.
60
+ - ZIP extraction limits for archive count, decompressed size, and ratio checks.
61
+ - `getLayers()`, `getDataset()`, `getExtent()`, `getArcs()`, and export helpers.
62
+ - `DisplayArcs` LOD wrapper for large datasets.
63
+
64
+ ## 4. Layers and Rendering
65
+
66
+ Layer types:
67
+
68
+ - `line`
69
+ - `fill`
70
+ - `circle`
71
+
72
+ Layer APIs:
73
+
74
+ - `map.addLayer(layer, before?)`
75
+ - `map.removeLayer(id)`
76
+ - `map.getLayer(id)`
77
+ - `map.getLayers()`
78
+ - `map.moveLayer(id, beforeId?)`
79
+ - `map.setLayerVisibility(id, visibility)`
80
+ - `map.setPaintProperty(id, name, value)`
81
+
82
+ Rendering capabilities:
83
+
84
+ - HTML5 Canvas 2D renderer.
85
+ - Batched stroke drawing for topology arcs.
86
+ - Shared-arc line rendering so polygon boundaries can be drawn once.
87
+ - Polygon fill rendering with even-odd fill handling.
88
+ - Point rendering.
89
+ - Highlight overlay rendering.
90
+ - Edit and draw overlay rendering.
91
+
92
+ ## 5. Camera and View
93
+
94
+ Camera methods:
95
+
96
+ - `jumpTo()`
97
+ - `setCenter()`
98
+ - `setZoom()`
99
+ - `easeTo()`
100
+ - `flyTo()`
101
+ - `panTo()`
102
+ - `panBy()`
103
+ - `zoomTo()`
104
+ - `fitBounds()`
105
+ - `stop()`
106
+
107
+ View and coordinate methods:
108
+
109
+ - `getCenter()`
110
+ - `getZoom()`
111
+ - `getExtent()`
112
+ - `setExtent()`
113
+ - `reset()`
114
+ - `project()`
115
+ - `unproject()`
116
+ - `resize()`
117
+
118
+ Camera events:
119
+
120
+ - `movestart`
121
+ - `move`
122
+ - `moveend`
123
+ - `zoomstart`
124
+ - `zoom`
125
+ - `zoomend`
126
+
127
+ ## 6. Named Interaction Handlers
128
+
129
+ `emap` exposes built-in interactions as named handlers. Third-party apps can use
130
+ these directly without using bundled UI controls.
131
+
132
+ | Handler | Purpose |
133
+ |---|---|
134
+ | `map.dragPan` | Pointer drag map panning |
135
+ | `map.scrollZoom` | Wheel zoom |
136
+ | `map.clickSelect` | Click selection |
137
+ | `map.boxSelect` | Active-tool rectangular selection |
138
+ | `map.lassoSelect` | Free-form lasso selection |
139
+ | `map.vertexEdit` | Topology-aware vertex editing |
140
+ | `map.drawFeature` | Point, polyline, and polygon drawing |
141
+ | `map.transformFeature` | Translate, rotate, or scale selected features |
142
+
143
+ Uniform handler surface:
144
+
145
+ - `enable()`
146
+ - `disable()`
147
+ - `isEnabled()`
148
+ - `setOptions()`
149
+ - `getOptions()`
150
+ - `handlePointer()`
151
+ - optional `getCursor()`
152
+
153
+ `HandlerManager` arbitrates pointer events with priority ordering, exclusive
154
+ gesture locking, and cursor resolution.
155
+
156
+ ## 7. Selection, Hit Testing, and Feature Events
157
+
158
+ Selection APIs:
159
+
160
+ - `map.select(features, { mode })`
161
+ - `map.deselect(features)`
162
+ - `map.clearSelection()`
163
+ - `map.getSelection()`
164
+ - `map.isSelected(feature)`
165
+ - `map.deleteSelected()`
166
+
167
+ Selection modes:
168
+
169
+ - `replace`
170
+ - `add`
171
+ - `toggle`
172
+
173
+ Feature querying:
174
+
175
+ - Point query: `map.queryFeatures([x, y], options?)`
176
+ - BBox query: `map.queryFeatures([[x1, y1], [x2, y2]], options?)`
177
+ - Layer filtering through `{ layers: [...] }`
178
+ - Lazy Flatbush spatial indexes.
179
+ - `map.prebuildSpatialIndex(sourceId?)`
180
+ - `map.clearSpatialIndex(sourceId?)`
181
+
182
+ Delegated events:
183
+
184
+ - `feature:click`
185
+ - `feature:hover`
186
+ - `feature:enter`
187
+ - `feature:leave`
188
+ - `mousemove`
189
+
190
+ Feature accessor:
191
+
192
+ - `map.features.get(ref)`
193
+ - `map.features.iter(sourceId, layerId)`
194
+ - `map.features.count(sourceId, layerId)`
195
+
196
+ Highlighting:
197
+
198
+ - `map.setHighlightedFeatures(features, style?)`
199
+ - `map.clearHighlightedFeatures()`
200
+ - `map.setHighlightStyle(style)`
201
+
202
+ ## 8. Editing Model
203
+
204
+ Undoable command families:
205
+
206
+ - Vertex commands: `VertexMoveCommand`, `VertexInsertCommand`,
207
+ `VertexDeleteCommand`.
208
+ - Feature commands: `FeatureCreateCommand`, `FeatureDeleteCommand`,
209
+ `BulkFeatureDeleteCommand`, `FeatureTranslateCommand`, `FeatureAffineCommand`.
210
+ - Topology commands: `SplitSharedArcsCommand`.
211
+ - Dataset replace: `DatasetReplaceCommand`.
212
+ - Attribute and schema commands: `FeaturePropertyChangeCommand`,
213
+ `FieldAddCommand`, `FieldRemoveCommand`, `FieldRenameCommand`.
214
+ - Multi-command envelope: `CompositeCommand`.
215
+
216
+ Session editing:
217
+
218
+ - `map.editSession.translateSelected(dx, dy)`
219
+ - `map.editSession.rotateSelected(angle, origin?)`
220
+ - `map.editSession.scaleSelected(sx, sy?, origin?)`
221
+ - `map.editSession.beginTranslateSession(options?)`
222
+ - `map.editSession.beginAffineSession(matrix?, origin?)`
223
+
224
+ Transactions:
225
+
226
+ - `map.beginTransaction()`
227
+ - `tx.commit(label?)`
228
+ - `tx.rollback()`
229
+
230
+ Undo/redo:
231
+
232
+ - `map.undo()`
233
+ - `map.redo()`
234
+ - `map.canUndo()`
235
+ - `map.canRedo()`
236
+ - `map.clearHistory()`
237
+ - `map.bindHistoryShortcuts()`
238
+
239
+ ## 9. Mapshaper Operations
240
+
241
+ All operations live under `map.ops` and return `Promise<OpResult>`.
242
+
243
+ Geometry and topology:
244
+
245
+ - `clipLayer`
246
+ - `eraseLayer`
247
+ - `dissolveLayer`
248
+ - `bufferLayer`
249
+ - `simplifyLayer`
250
+ - `projectLayer`
251
+ - `affineLayer`
252
+ - `cleanLayer`
253
+ - `snapLayer`
254
+ - `rebuildTopology`
255
+ - `checkGeometry`
256
+
257
+ Set and overlay operations:
258
+
259
+ - `mosaicLayer`
260
+ - `unionLayers`
261
+ - `divideLayer`
262
+ - `intersectionPointsLayer`
263
+
264
+ Geometry conversion:
265
+
266
+ - `pointsLayer`
267
+ - `linesLayer`
268
+ - `polygonsLayer`
269
+ - `innerlinesLayer`
270
+ - `explodeLayer`
271
+
272
+ Layer management:
273
+
274
+ - `renameLayer`
275
+ - `mergeLayers`
276
+ - `splitLayer`
277
+ - `dropLayer`
278
+
279
+ Filtering and attributes:
280
+
281
+ - `applyExpression`
282
+ - `filterFeatures`
283
+ - `filterIslands`
284
+ - `filterSlivers`
285
+ - `filterGeom`
286
+ - `sortFeatures`
287
+ - `uniqueFeatures`
288
+ - `filterFields`
289
+ - `renameFields`
290
+ - `joinTable`
291
+ - `dataFill`
292
+
293
+ Selection-driven:
294
+
295
+ - `mergeSelected`
296
+
297
+ Operation runtime features:
298
+
299
+ - Parsed command objects instead of shell strings.
300
+ - Effect-aware selection preservation.
301
+ - Dataset-replace undo barriers.
302
+ - Optional worker routing.
303
+ - Structured `OpResult` and `OpError` responses.
304
+
305
+ ## 10. Validation
306
+
307
+ Validator registry:
308
+
309
+ - `map.validators.register(validator)`
310
+ - returned unregister function
311
+ - `validationfailed` event
312
+
313
+ Built-in validator:
314
+
315
+ - `topologyValidator(options)`
316
+
317
+ Validation runs after committed history changes. The engine reports failures and
318
+ leaves application policy decisions to the host app.
319
+
320
+ ## 11. Worker Offloading
321
+
322
+ Worker options:
323
+
324
+ - `useWorker: false | true | 'auto'`
325
+ - `workerMode` compatibility alias
326
+ - `workerThreshold`
327
+ - `workerUrl`
328
+ - `workerPoolSize`
329
+ - `workerPool`
330
+ - `workerRouting(info)`
331
+
332
+ Worker features:
333
+
334
+ - Lazy worker creation.
335
+ - Multiple worker instances through `MapshaperWorkerPool`.
336
+ - Command-family-aware routing.
337
+ - Cancellation through `map.cancelWorkerJobs()`.
338
+ - Transferable arc buffers where supported by the Mapshaper pack/unpack path.
339
+
340
+ ## 12. Snapshots and Export
341
+
342
+ Map snapshot APIs:
343
+
344
+ - `map.exportSnapshot(options?)`
345
+ - `map.loadSnapshot(input, options?)`
346
+
347
+ Source export formats:
348
+
349
+ - GeoJSON
350
+ - TopoJSON
351
+ - Shapefile ZIP
352
+
353
+ Snapshots use Mapshaper pack data with `emap` source metadata for multi-source
354
+ restore.
355
+
356
+ ## 13. Built-In Controls
357
+
358
+ | Control | Purpose |
359
+ |---|---|
360
+ | `NavigationControl` | Zoom in, zoom out, reset |
361
+ | `StatusControl` | Pointer coordinates and CRS |
362
+ | `BasemapControl` | MapLibre raster basemap toggle |
363
+ | `HistoryControl` | Undo/redo |
364
+ | `EditToolbar` | Combined edit toolbar |
365
+ | `VertexEditControl` | Button shell over `map.vertexEdit` |
366
+ | `DrawFeatureControl` | Button shell over `map.drawFeature` |
367
+ | `BoxSelectControl` | Thin shell over `map.boxSelect` |
368
+ | `LassoSelectControl` | Thin shell over `map.lassoSelect` |
369
+ | `SimplifyControl` | Simplification UI |
370
+
371
+ Custom controls implement:
372
+
373
+ - `onAdd(map): HTMLElement`
374
+ - `onRemove(map?)`
375
+
376
+ Control positions:
377
+
378
+ - `top-left`
379
+ - `top-right`
380
+ - `bottom-left`
381
+ - `bottom-right`
382
+
383
+ ## 14. Events
384
+
385
+ Typed map events include:
386
+
387
+ - `data`
388
+ - `error`
389
+ - `selectionchange`
390
+ - `historychange`
391
+ - `editmodechange`
392
+ - `validationfailed`
393
+ - `crschange`
394
+ - `resize`
395
+ - camera lifecycle events
396
+
397
+ `EmapEventMap` is augmentable through TypeScript module augmentation for
398
+ application-specific events.
399
+
400
+ ## 15. CRS and Geometry Utilities
401
+
402
+ Exports:
403
+
404
+ - `Bounds`
405
+ - `AffineTransform`
406
+ - `Viewport`
407
+ - `Projection`
408
+ - `DEFAULT_CRS`
409
+ - `inferFromPrj`
410
+ - `normalizeCrsString`
411
+ - `resolveDatasetCRS`
412
+
413
+ Mapshaper topology types are also exported for consumers that need to build
414
+ custom commands or inspect raw datasets.
415
+
416
+ ## 16. Public Advanced Surfaces
417
+
418
+ Advanced exports include:
419
+
420
+ - `DefaultMapshaperAdapter`
421
+ - `getDefaultMapshaperAdapter`
422
+ - `setDefaultMapshaperAdapter`
423
+ - `MapshaperWorkerPool`
424
+ - `HandlerManager`
425
+ - built-in handler classes
426
+ - `CanvasPainter`
427
+ - `EditOverlayRenderer`
428
+ - edit command classes
429
+ - `ValidatorRegistry`
430
+ - `Err`, `OK`, and `okValue`
431
+ - layer paint resolvers
432
+ - CLI safety helpers: `quoteCliArg`, `isValidLayerName`, `isValidFieldName`
433
+
434
+ ## 17. Security and Constraints
435
+
436
+ - Browser runtime only.
437
+ - No server dependency.
438
+ - Mapshaper internals are isolated behind `MapshaperAdapter`.
439
+ - Expression-bearing APIs default to `expressionPolicy: 'disabled'`.
440
+ - Apps must opt in to `expressionPolicy: 'trusted'` before executing trusted
441
+ expression strings.
442
+ - Worker assets must be served by the host app when worker offloading is used.
443
+
444
+ ## 18. Examples and Tests
445
+
446
+ - Manual examples: `test/examples/*.html`.
447
+ - Unit tests: `src/**/*.test.ts`.
448
+ - Type tests: `*.test-d.ts`.
449
+ - Build and verification:
450
+
451
+ ```bash
452
+ cmd /c npm run typecheck
453
+ cmd /c npm run test:run
454
+ cmd /c npm run build
455
+ ```