@zwishing/emap 0.1.3 → 0.3.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +250 -1
  2. package/FEATURES.md +455 -0
  3. package/README.md +415 -205
  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 +54 -0
  9. package/dist/core/handlers/click-select.d.ts +31 -0
  10. package/dist/core/handlers/drag-pan.d.ts +28 -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 +41 -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 +33 -4
  33. package/dist/ui/box-select-control.d.ts +13 -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,253 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-05-21
11
+
12
+ ### Added
13
+
14
+ - DX Phase 1 (toward 0.3.0): `Handler` interface + `HandlerManager`; the pan
15
+ and wheel interactions are now named handlers `map.dragPan` /
16
+ `map.scrollZoom` with `enable()/disable()/isEnabled()/setOptions()`.
17
+ Enables selective interaction toggling (e.g. disable wheel, keep pan)
18
+ without reaching into private internals. No behavior change for the
19
+ default path (`interactive` unset/true): the underlying pan/wheel
20
+ primitives are unchanged and all existing tests pass byte-identically.
21
+ One intentional fix: `MapOptions.interactive: false` was previously a
22
+ declared-but-unwired no-op — it is now functional and disables pan+zoom
23
+ as the option always implied. See
24
+ `docs/superpowers/specs/2026-05-17-emap-maplibre-dx-design.md`.
25
+ - DX Phase 2a (toward 0.3.0): `HandlerManager` is now the single
26
+ pointer-event arbiter (`attach`/`detach`, normalized `NormalizedPointerEvent`,
27
+ priority-ordered dispatch with exclusive-gesture locking). `dragPan` pans
28
+ via `handlePointer` and owns no DOM listeners; the old internal pan
29
+ primitive (`src/core/drag-pan-handler.ts`) is removed. No pan behavior
30
+ change; this is the seam P2b's selection handlers slot into.
31
+ - DX Phase 2b (toward 0.3.0): three opt-in named selection handlers —
32
+ `map.clickSelect`, `map.boxSelect`, `map.lassoSelect` (each a `Handler`
33
+ with `enable/disable/isEnabled/setOptions/getOptions`). Driven by the
34
+ Phase-2a pointer arbiter; `box`/`lasso` sit above `dragPan` so
35
+ `shift`+drag selects (when enabled) instead of panning, with no
36
+ `addControl` needed. Shared pure `resolveMode` (configurable
37
+ modifier→`SelectMode` table) and a shared `select-geometry` module
38
+ replace the duplicated private `_modeFromEvent`. `boxSelect` and
39
+ `lassoSelect` are mutually exclusive (`enable()` throws if the other is
40
+ on). Disabled by default → `new Emap()` behavior is unchanged; the
41
+ legacy `BoxSelectControl`/`LassoSelectControl` keep working unchanged
42
+ (thin-shell rewrite is Phase 2c).
43
+ - DX Phase 2c (toward 0.3.0): `BoxSelectControl` / `LassoSelectControl`
44
+ are now button-only thin shells over the P2b `map.boxSelect` /
45
+ `map.lassoSelect` handlers (the ~490 lines of duplicated
46
+ listener/overlay/hit-test/select logic deleted) — public class,
47
+ constructor options, 0×0 anchor DOM, selection behaviour, and the
48
+ dashed-lasso visual are byte-identical (the handler now honours
49
+ `strokeDasharray`, default `'4 3'`). Adding both controls now surfaces
50
+ the handlers' mutual-exclusivity error instead of silently conflicting.
51
+ Duplicated types consolidated to one declaration each with re-export
52
+ shims (every public import path unchanged): `SelectMode` →
53
+ `core/handlers/select-mode`, `BoxSelectStyle` →
54
+ `core/handlers/box-select`, `LassoSelectStyle`/`LassoStyle` →
55
+ `core/handlers/lasso-select` (`LassoStyle` widened additively to include
56
+ the optional `strokeDasharray`). No arbiter/dispatch change.
57
+ - DX Phase 3 (toward 0.3.0): delegated feature events —
58
+ `map.on('feature:click'|'feature:hover'|'feature:enter'|'feature:leave',
59
+ { source?, layers? }?, cb)`. Fed by the Phase-2a pointer arbiter, hit-test
60
+ reuses the existing `FeatureQuery` (no new path), decoupled from
61
+ selection. Zero cost when no `feature:*` listener is registered (the
62
+ pointer sink is installed lazily). Canonical highlight recipe:
63
+ `map.on('feature:enter', {}, e => map.setHighlightedFeatures([e.ref]));
64
+ map.on('feature:leave', {}, () => map.clearHighlightedFeatures());` —
65
+ replaces the manual mousemove-poll + highlight-sync boilerplate.
66
+ - DX Phase 4 (toward 0.3.0): opened the emap design-token namespace —
67
+ `controls.css` now declares 9 `--emap-*` custom properties on `:root`
68
+ (`accent`, `accent-soft`, `surface-bg`, `surface-border`,
69
+ `surface-shadow`, `radius`, `hover-bg`, `danger`, `font`) with the
70
+ current literal values as defaults. The vertex-edit right-click menu
71
+ ("删除节点") migrated off inline styles + JS `mouseenter`/`mouseleave`
72
+ hover toggle onto class-based rules (`.emap-ctx-menu` /
73
+ `.emap-ctx-menu-item` with `:hover`) consuming the tokens. Default
74
+ rendering is byte-identical; themers override `--emap-*` on `:root` (or
75
+ a parent) to restyle. Existing controls' literal values are unchanged
76
+ — namespace opened but not retro-applied (follow-up phases may opt in).
77
+ - DX Phase 5 (toward 0.3.0): vertex editing is now the arbiter-driven
78
+ `map.vertexEdit` handler (`enable/disable/isEnabled/setOptions/
79
+ getOptions`), consistent with the other handlers — usable without
80
+ `addControl`. The arbiter gained a `contextmenu` event kind (right-click
81
+ vertex delete). `VertexEditControl` is now a button-only thin shell over
82
+ `map.vertexEdit`; its public class, constructor options, button DOM,
83
+ `editmodechange`/edit-mode behavior, undo commands, and the
84
+ `vertex_moved` event are byte-identical. Disabled by default →
85
+ `new Emap()` behavior unchanged. (P4 will migrate the context-menu
86
+ inline styles to CSS tokens.)
87
+ - DX Phase 6 (toward 0.3.0): feature drawing is now the arbiter-driven
88
+ `map.drawFeature` handler (`enable/disable/isEnabled/setOptions/
89
+ getOptions`) — usable without `addControl`. The arbiter gained a
90
+ `dblclick` event kind (finish polyline/polygon); `Escape`-to-cancel is a
91
+ handler-owned key listener. `enable()` throws if `source`/`type` are
92
+ unset. `DrawFeatureControl` is now a button-only thin shell over
93
+ `map.drawFeature`; its public class, constructor options (incl. `icon`),
94
+ button DOM, `feature_created` event, `FeatureCreateCommand`, and the
95
+ cross-mode auto-deactivate are byte-identical. Disabled (and
96
+ unconfigured) by default → `new Emap()` behavior unchanged.
97
+ - DX Phase 7 (toward 0.3.0): feature transform is now the arbiter-driven
98
+ modal `map.transformFeature` handler (`setOptions({ mode:
99
+ 'translate'|'rotate'|'scale' })`, `enable/disable/isEnabled/getOptions`).
100
+ Selection is delegated to `clickSelect` (click = replace, shift+click =
101
+ add, click on empty space = clear); `transformFeature` owns no
102
+ selection logic — it press-drags an already-selected feature:
103
+ pointerdown over a selected feature opens the matching `editSession`,
104
+ drag streams the delta, pointerup commits one undo entry, `Escape`
105
+ cancels. `EditMode` gained `'transform'` (enters the modal pan-disable +
106
+ `editmodechange` like vertex/draw; cross-deactivates with
107
+ vertexEdit/drawFeature). The headless `map.editSession` API is unchanged
108
+ — this only absorbs the pointer→session boilerplate. Disabled by default
109
+ → `new Emap()` behavior unchanged. The `feature-translate` /
110
+ `feature-rotate-scale` examples are rewritten to the one-line recipe.
111
+ - DX Phase 8 (toward 0.3.0): delegated `map.on('mousemove', cb)` event
112
+ (payload `{ point, mapCoord, originalEvent }`), fed by the Phase-2a
113
+ pointer arbiter via the same sink mechanism as the Phase-3 `feature:*`
114
+ events — exported `PointerMoveEvent` type. `StatusControl` no longer
115
+ hand-wires a raw canvas `mousemove`; it subscribes the delegated event
116
+ (public class/options/DOM/coordinate-string unchanged). Zero cost when
117
+ no `mousemove`/`feature:*` listener is registered (the pointer sink is
118
+ installed lazily and shared). One intentional, documented behavior
119
+ change: the coordinate readout pauses during an exclusive pan/drag
120
+ gesture (consistent with `feature:hover`) and resumes on release.
121
+ Disabled-by-default cost model unchanged → `new Emap()` behavior
122
+ identical.
123
+ - DX Phase 9 (toward 0.3.0): MapLibre-parity camera API — `map.jumpTo`,
124
+ `setCenter`, `setZoom`, `easeTo`, `panTo`, `panBy`, `zoomTo`, `flyTo`
125
+ (van-Wijk parabolic), `fitBounds`, `stop` (all chainable, return
126
+ `this`), built on the existing `Tween`/`Viewport`. Fires a
127
+ `movestart`/`move`/`moveend` + `zoomstart`/`zoom`/`zoomend` lifecycle.
128
+ Camera state is `{ center (projected/data coords, = the Phase-8
129
+ `mapCoord` space), scale }`; `zoom` is an optional Web-Mercator
130
+ convenience (`Projection.getScale` inverse) — non-Web-Mercator CRSs
131
+ pass `{ scale }`. 2D only (no bearing/pitch). A new camera call (or a
132
+ user pan/wheel) interrupts a running animation; the interrupted move
133
+ still fires its `moveend`. `getCenter`/`getZoom`/`setExtent`/`reset`/
134
+ `zoomIn`/`zoomOut` are byte-identical; default `new Emap()` behaviour
135
+ unchanged (no animation runs until a camera method is called).
136
+ - DX Phase 10 (toward 0.3.0): typed `EmapEventMap` — `map.on` / `off` /
137
+ `once` now give
138
+ event-name autocomplete and payload inference for the camera lifecycle
139
+ (`movestart`/`move`/`moveend`/`zoomstart`/`zoom`/`zoomend`) and the
140
+ `data`/`error`/`selectionchange`/`historychange`/`editmodechange`/
141
+ `crschange`/`resize`/`validationfailed` events. `EmapEventMap` is an
142
+ augmentable `interface` (`declare module` to register custom events);
143
+ unknown event names still compile (escape hatch preserved). Compile-time
144
+ only — zero runtime change. Exported: `EmapEventMap`, `CameraEvent`.
145
+ - DX Phase 11 (toward 0.3.0): per-handler cursor — each `Handler` may
146
+ implement `getCursor(): string | null`; `HandlerManager` arbitrates one
147
+ effective cursor (active gesture > priority order > default) and writes
148
+ it to the map element via `refreshCursor()` / on every dispatch. Adds
149
+ the previously-absent `grab`/`grabbing` pan cursors and the draw
150
+ crosshair (the old `.emap-draw-active` CSS rule was dead); the ad-hoc
151
+ `.emap-edit-active`/`.emap-draw-active` CSS-class mechanism is removed
152
+ (single source of truth). No dispatch/priority behaviour change.
153
+
154
+ ### Changed
155
+
156
+ - **`LassoStyle` widened (additive, back-compat):** the public
157
+ `LassoStyle` type (`@zwishing/emap` index, from
158
+ `core/handlers/lasso-select`) gained an optional `strokeDasharray?:
159
+ string` field (default `'4 3'`). Every existing 3-field value remains
160
+ assignable; consumers passing an extra `strokeDasharray` to a lasso
161
+ handler/control now see the dashed visual previously only possible via
162
+ the `LassoSelectControl` surface.
163
+ - **Adding `BoxSelectControl` + `LassoSelectControl` together now
164
+ throws:** the P2b handlers are mutually exclusive (one of `enable()`
165
+ throws `cannot enable boxSelect: lassoSelect is enabled`). The legacy
166
+ controls silently coexisted with undefined behavior; the P2c thin
167
+ shells surface the conflict honestly. Install one, OR call
168
+ `map.boxSelect.disable()` before enabling the other (see the updated
169
+ `test/examples/lasso-select.html` for the canonical toggle recipe).
170
+ - Release metadata now targets `0.3.0`, matching the handler-centric public API
171
+ documented in README/FEATURES.
172
+
173
+ ### Fixed
174
+
175
+ - Made the bundle self-contained smoke test portable on Windows by resolving
176
+ local CLI shims through `cmd /c`, adding explicit `esbuild` dev dependency
177
+ coverage, and relaxing the GeoJSON bundle-load timeout for slower full-suite
178
+ runs.
179
+ - Completed the typed event surface: `EmapEventMap` now includes the delegated
180
+ `mousemove` and `feature:*` events, so facade authors can index the event map
181
+ for those 0.3.0 APIs instead of relying only on `map.on(...)` overloads.
182
+
183
+ ### Documentation
184
+
185
+ - Added third-party release guidance: static asset deployment, full custom
186
+ box-selection wiring without bundled controls, known limitations, API
187
+ stability boundaries, and a release checklist. The build now cleans `dist`
188
+ before production output so stale declaration files are not packed.
189
+ - Refreshed the project documentation for the current handler-centric public
190
+ API: rewrote `README.md` around third-party integration, headless handler
191
+ usage, custom UI patterns, feature events, editing, worker setup, theming,
192
+ and built-in controls; moved `docs/FEATURES.md` to the package root as
193
+ `FEATURES.md` and updated it as the canonical capability matrix. Removed
194
+ the mandatory OpenSpec workflow instructions from `AGENTS.md` and
195
+ `CLAUDE.md`, keeping them focused on current project architecture and
196
+ contribution rules.
197
+ - Documented the explicit 0.2.0 loading contract: `setData()` resolves only
198
+ after the dataset is populated and `data{reason:'load'}` has fired, and
199
+ `map.addSource()` is order-independent (back-fills an already-loaded
200
+ source). Added a troubleshooting note — a blank map with no error after
201
+ "upgrading to 0.2.0" is the *pre-0.2.0* fire-and-forget symptom and
202
+ indicates a stale install, not an emap defect (a reopened Friction #9
203
+ report was root-caused to this; the published 0.2.0 runtime was verified
204
+ to settle the promise after the data event).
205
+ - Modernized all 41 `test/examples/*.html` to the canonical awaitable
206
+ recipe (`await source.setData(...)` → `map.addSource(...)` →
207
+ `map.setExtent(...)` in a `try/catch`), replacing the older event-driven
208
+ `source.on('data'/'error')` load pattern. Examples now match the README
209
+ "Loading contract" verbatim, removing the example/doc divergence that
210
+ contributed to the reopened #9 misdiagnosis. Per-file status text,
211
+ post-load logic, and multi-file/Promise-wrapper variants preserved.
212
+
213
+ ### Tested
214
+
215
+ - Added `src/map/setdata-addsource-ordering.test.ts`: end-to-end regression
216
+ proving both `addSource`→`await setData` and `await setData`→`addSource`
217
+ populate the dataset before the promise resolves and schedule a render
218
+ (guards the loading contract against regression).
219
+
220
+ ## [0.2.0] - 2026-05-17
221
+
222
+ ### Changed
223
+
224
+ - **BREAKING:** `TopologySource.setData()` now returns
225
+ `Promise<TopologySource>` instead of `this`. It resolves once the import
226
+ completes and rejects if it fails, so `await source.setData(...)` is now the
227
+ canonical way to wait for the dataset (previously `await` resolved on the
228
+ next microtask, before the data existed — a silent footgun that left the
229
+ map blank). The `data` / `error` events still fire unchanged for
230
+ event-driven consumers, and last-write-wins semantics are preserved (a
231
+ superseded call's promise resolves early so awaiting it never hangs).
232
+ Fluent chaining off the return value (`source.setData(x).getLayers()`) no
233
+ longer works — `await` first. `fromUrl` / `fromBytes` now delegate to the
234
+ awaitable `setData` internally.
235
+ - `TopologySource.getLayers()` is now typed `MapshaperLayer[]` instead of
236
+ `any[]`, so `name` / `geometry_type` are reachable without an `any` cast at
237
+ every consumer. Runtime behavior is unchanged (additive type only).
238
+
239
+ ### Fixed
240
+
241
+ - Corrected `test/examples/*.html` (`box-select`, `draw-snap-edge`,
242
+ `lasso-select`, `polygon-fill`) that passed the camelCase `sourceLayer` key
243
+ to `addLayer` specs. The `LayerSpecification` only reads the canonical
244
+ MapLibre-style `'source-layer'`, so the camelCase key was silently ignored
245
+ and resolution fell back to the first layer — a real bug in `polygon-fill`,
246
+ which targets a specific layer. Examples now use `'source-layer'`.
247
+ - `npm run dev-build` was broken: the `prefer-browser-mapshaper-modules`
248
+ Rollup plugin only matched the terser-minified ternary form of mapshaper's
249
+ module loader, so it patched the production vendor but threw `Unable to
250
+ patch mapshaper module loader` on the unminified dev vendor (terser does
251
+ not run in dev). `dist/emap-dev.js` — referenced by every
252
+ `test/examples/*.html` — could not be built at all. Added a second pattern
253
+ matching the unminified `if / else-if / else` loader block and reordering
254
+ it to prefer `window.modules` over `require` (same intent as the minified
255
+ path). Dev and prod builds now both patch the vendor correctly.
256
+
10
257
  ## [0.1.3] - 2026-05-17
11
258
 
12
259
  ### Fixed
@@ -75,7 +322,9 @@ Initial public release.
75
322
  - Distributed control stylesheet as `dist/emap.css` (importable via
76
323
  `@zwishing/emap/style.css`).
77
324
 
78
- [Unreleased]: https://github.com/zwishing/emap/compare/v0.1.3...HEAD
325
+ [Unreleased]: https://github.com/zwishing/emap/compare/v0.3.0...HEAD
326
+ [0.3.0]: https://github.com/zwishing/emap/compare/v0.2.0...v0.3.0
327
+ [0.2.0]: https://github.com/zwishing/emap/compare/v0.1.3...v0.2.0
79
328
  [0.1.3]: https://github.com/zwishing/emap/compare/v0.1.2...v0.1.3
80
329
  [0.1.2]: https://github.com/zwishing/emap/compare/v0.1.1...v0.1.2
81
330
  [0.1.1]: https://github.com/zwishing/emap/compare/v0.1.0...v0.1.1