Releases: softwarity/draw-adapter
Releases · softwarity/draw-adapter
v0.3.0
- Add: anchored marker widgets — a generic, domain-free DOM "card" pinned at a
lon/lat, built from a tiny box-layout tree (vbox/hbox +glyph/text/coord),
with an inline-editabletextbacked by a real<input>(caret, IME, paste, mobile
keyboard) that auto-grows to its content. NewMapAdapter.setWidgets(MarkerWidget[])
(declarative, diffed byidlikesetOverlay— a focused input keeps its focus/caret
across re-setWidgets),onWidgetEdit({ id, value })(per keystroke), and
setCoordFormat(fn)(formats the livecoordline). Selection/move reuse the existing
pointer model: a card click/drag surfaces throughonPointeras a
{ overlay: "widget", props: { id } }hit (carrying the real lon/lat), and the card never
drives map pan/zoom (an input press just edits). One implementation across all three
engines — the card rides each engine's native anchored-overlay primitive (MapLibre
Marker/ OpenLayersOverlay/ LeafletdivIcon), so it tracks per-frame through
pan/zoom and stays screen-upright; Pointer Events ⇒ touch works.padding/radiusreuse
theTextBoxSize/TextBoxRadiuspresets. Implemented on all 3 engines +FakeAdapter
(.setWidgets,.onWidgetEdit,.editWidget(id, value),.clickWidget(id)). New types
MarkerWidget/WidgetBox/WidgetNode/WidgetGlyph/WidgetText/WidgetCoord/
WidgetOrigin/WidgetEdit; new exportdefaultCoordFormat. Thecontrolfield is left
open for futuregauge/dial/carousel(onlyinputnow). Purely additive — no
existing consumer is affected. - Add: widgets can carry a delete button —
MarkerWidget.deletable: trueshows a bare
×in the top-right corner that firesMapAdapter.onWidgetDelete({ id }); the lib never removes
the card (the consumer drops theidfrom its nextsetWidgets). It's a separate element from
the card body, so an input-only card is still deletable, and it's excluded from snapshots.
Also new:text.uppercase— an editable input enters and emits its value in upper case
(caret-preserved); a static label displays upper case. - Add:
snapshot()now includes the widget cards (in their static, non-editable form —
each input rendered as its value) on MapLibre and OpenLayers, via aforeignObject
composite. Safe by design: the card-less PNG is produced before anyforeignObjectis
drawn, so a tainted canvas (e.g. on Safari) degrades to the card-less snapshot instead of
failing. Leafletsnapshot()is still unsupported, so its widgets aren't captured yet. - Fix: a
clicknow carries the hit captured at itsdown(atomic press) instead of
re-running the hit-test at click time — kills an intermittent select → immediate-deselect
where the trailingclickresolved to no hit: the select handler had re-rendered the feature
(Leaflet drops its hover state, OpenLayers'singleclickis ~250 ms delayed and races the
re-render). Two further nets fix the first click after re-focusing the window (whoseup
is often eaten by the OS focus gesture, leavingdraggingstuck — which only cleared after a
fresh map click): (a) a move with no button held finalises the press (emits the missing
up+ clears state) — it fires on the very move toward the element, before the click; and
(b) windowblurpurges the press state. All 3 engines. Pure robustness — no API change. - Add:
MapAdapter.onBlur(cb)— fires when the map's window loses focus (the user
switches to another window/app). The adapter stays domain-free and never changes selection
itself; this is the signal so the consumer can deselect the active element (e.g. so a marker
widget stops looking editable once you've left the window). All 3 engines +FakeAdapter
(.blur()helper). - Fix (MapLibre + OpenLayers): the
clickis now synthesized from the release (a
down+upat one spot, reusing thedownhit) instead of the engine's native click event —
OL's was a debouncedsingleclick(~250 ms; a quick second click became adblclick, so
click-away-to-deselect needed several clicks), and MapLibre's nativeclickgets swallowed
by the OS on the first click after re-focusing the window (which produced a select→deselect on
that click). Both now register on the first click, consistent with Leaflet.dblclickis
unchanged (native). No API change. - Add (camera + container):
getBounds()([west, south, east, north]),getZoom(),
getContainer(), andfitBounds([w,s,e,n], { padding? })on all 3 engines +FakeAdapter.
fitBoundsdrives the host camera (the one legit case — frame your own drawing); documented
"use sparingly". (Audit #1 + #4.) - Add:
setOverlayVisible(id, visible)— show/hide an overlay layer without dropping its
data (toggle reference layers / masks / guides); lossless vs. pushing an empty FC. (Audit #3.) - Add: right-click →
onPointerwithtype: "contextmenu"(the browser menu is suppressed),
carrying the hit + lon/lat — e.g. finish a polygon / delete a vertex. All 3 engines +FakeAdapter
(send("contextmenu", …)). (Audit #5.) - Add (widgets): action buttons on the card edges/corners —
MarkerWidget.buttons: [{ event, place?, svg?, bordered? }]fireonWidgetAction({ id, event }).
placeis an enum (top/bottom/left/right· the four corners ·edges/h-edges/v-edges
·corners/top-corners/bottom-corners/left-corners/right-corners) or an array, unioned
and deduped (e.g.["left-corners","top-corners"]⇒ 3 corners). Domain-free: the consumer names
theeventand decides what it does (e.g. "draw another area attached to this panel" ⇒ a
multipolygon + a 2nd leader, all consumer-side).FakeAdapter.actionWidget(id, event). - Add: the
upevent now carries the real release coordinate on OpenLayers & Leaflet too
(MapLibre already did) — finishing audit #2 (was{0,0}). - Change (cleanup):
PointerEvent.hitis now the exportedHittype instead of a duplicate
inline shape — structurally identical, non-breaking. (Audit #8.) - Fix (MapLibre touch): restored tap-to-select on touch — the release-synthesized click doesn't
fire on a finger tap (nomouseup), so a deduped native-click fallback covers touch taps. (Note:
freehand drawing on touch is still OpenLayers-only; ML/Leaflet drawing stays mouse-based — the
remaining touch chantier #7.)
v0.2.9
- Add:
PointerEventcarriesctrlKey/metaKey/shiftKey/altKey(the live modifier
state, incl. onmove) on all 3 engines +FakeAdapter— lets consumers gate drag
behaviour on a held modifier (e.g. Ctrl/⌘ to translate rigidly instead of deform). All
optional + defaultfalse(non-breaking);FakeAdapter.send(...)takes an optional
modsarg. TreatctrlKey || metaKeyas "the modifier" (Ctrl on PC/Linux, ⌘ on Mac).
v0.2.8
- Add: per-feature label box controls on
textfeatures —textBoxSize
(small/medium/large, defaultmedium) for padding andtextBoxRadius
(none(default)/small/medium/round) for corners, on top oftextBackground/
textBorder. The box is drawn only when a fill and/or border is set, and rotates
with the text. New exported typesTextBoxSize/TextBoxRadius. - Fix/Add (MapLibre): the label box is now a per-feature 9-slice image built on
demand (styleimagemissing) from the feature'stextBackground/textBorder/
textBoxSize/textBoxRadius— so MapLibre finally honours per-feature box colours,
padding and corner radius (it previously drew one fixed white/black box). - Limitation: OpenLayers honours
textBoxSizeand the colours but not
textBoxRadius— its native text background is a rectangle (no corner radius).
v0.2.7
- Add:
MapAdapter.onKey(cb)— forwards a normalizedKeyEventon keydown while the map is focused (scoped to the container, multi-instance safe; editable targets skipped). Raw transport — the consumer maps keys to actions (e.g. Delete/Backspace ⇒ remove selection). Implemented on all 3 engines +FakeAdapter(.key()helper);bindKeyListenerexported. - Add: toolbar submenus — a
ToolbarItemwithchildren: ToolbarItem[]becomes a flyout that opens on hover (desktop) and click (touch), into the map based on the toolbar edge (top ⇒ below · bottom ⇒ above · left ⇒ right · right ⇒ left). Two modes: click (parent = fixed category; a child runs itsonClick) and toggle (toggle: true, a split button — the parent mirrors the selected child, becomes the active tool, and a parent click re-runs it). An outside press closes the flyout.ToolbarItem.onClickis now optional. - Add: built-in "lock map" toggle at the end of the bar (default on;
ToolbarOptions.lock: falsehides it) — freezes pan/zoom/rotate on all 3 engines so the map can't move while drawing. NewMapAdapter.setInteractive(enabled): while locked it wins over the controller's transientsetPanEnabled/setDoubleClickZoom(remembered and re-applied on unlock). NewToolbarItem.standalone(a utility button whose click doesn't change the active tool selection — also set on the snapshot button now). - Breaking: removed
ToolbarOptions.orientation— the bar's flow is now derived fromposition(top/bottom edge ⇒ horizontal row, left/right edge ⇒ vertical column), and the submenu flyout follows it (column vs row). Consumers must drop anyorientationthey passed. - Fix: OpenLayers toolbar now renders as a solid white bar with plain buttons (OL's default
.ol-controlbuttons were blue/translucent, so the bar looked like loose buttons).
v0.2.6
- Fix: double-click editing (insert a shape vertex / split a break point) now works on OpenLayers and Leaflet — previously only MapLibre did. The capture-phase press handler that stops the map pan on a draggable hit was also suppressing the engine's synthesized
dblclick, so it never reached the controller. OpenLayers now listens to the native viewportdblclick(its handles are canvas, so it fires reliably); Leaflet detects the double-click manually from press timing + position (its handle markers are recreated on every re-render, so the two clicks land on different DOM nodes and no nativedblclickis emitted at all). - Add: MapLibre call-out boxes — a 9-slice
icon-text-fitbackground box drawn behind any label that carriestextBackground, for parity with the nativebackgroundFillboxes on OpenLayers/Leaflet. - Change: call-out box padding increased on OpenLayers and Leaflet; sprite glyphs in Leaflet
divIcons now scale to (and centre within) the icon box instead of sitting at their intrinsic size; Leaflet multi-line labels honour\n(white-space: pre-line).
v0.2.5
- Fix: clipboard copy now works — the write is issued synchronously within the click (the capture promise is fed to
ClipboardItem), instead of after the captureawaitwhere Safari/Chrome silently reject it (lost user gesture). - Snapshot tooltip is now fixed per mode (e.g. "Snapshot: click to file — ⌘+click to clipboard").
v0.2.4
snapshot()gainshideOverlays?: string[]— overlay ids to hide only for the
capture (e.g. editing handles/guides), restored after, so the snapshot shows the
clean drawing without the construction chrome. Also on the toolbar config
(snapshot: { hideOverlays: [...] }).- Removed the
basemapsnapshot option: hiding only the tiles can't be done cleanly in
a generic, domain-free way (the host map's basemap vs. domain layers like FIR are
indistinguishable, and the GL canvas isn't guaranteed transparent).
v0.2.3
Adds a curtain shutter effect as capture feedback, plus clearer icons.
- A successful snapshot from the toolbar plays a brief shutter animation over the map —
two translucent blades close to the centre and reopen (the map stays faintly visible) —
visual confirmation that doubles as the "copied" feedback for the otherwise-silent
clipboard delivery. Opt out withsnapshot: { shutter: false }(defaulttrue). - Honours
prefers-reduced-motion(degrades to a single quick dim); the overlay is
pointer-events:noneand self-removes. The flash plays only on success. - The snapshot button icon is a camera; the two deliveries differ only by the lens —
filled for download (SNAPSHOT_ICON_SVG), an empty ring for clipboard
(SNAPSHOT_CLIPBOARD_ICON) — and the hover preview swaps between them. - New export
shutterFlash(container, { durationMs? })for manual use.
v0.2.2
Tidies the snapshot toolbar option — single object form, clearer naming.
ToolbarOptions.snapshotis now"none" | false | null | { quality?, onClick? }
(no more bare-preset string form).undefined⇒ defaults (a button); any
explicit falsy value (null/false/"none") hides it.- Renamed the size field
state→quality, and the typeSnapshotLevel→
SnapshotQuality("native" | "low" | "medium" | "high"—"none"moved out
to the option's union, where it belongs). - The toolbar button live-previews the delivery while hovered: holding the modifier
key swaps its icon to the alternate action (the tooltip is fixed — see 0.2.5). Key
listeners are scoped to the hover only (no global churn). NewToolbarItem.onRender
hook + exportedSNAPSHOT_CLIPBOARD_ICON.
v0.2.1
Snapshot can now download or copy to clipboard — additive, non-breaking.
snapshot()gainstarget("blob"default ·"download"·"clipboard") and
filename: it captures, optionally delivers the PNG, and always returns the Blob.- Toolbar: one camera button now offers both deliveries.
ToolbarOptions.snapshot
accepts{ state, onClick }—onClick("download"|"clipboard", default
"download") is the plain-click delivery; the other runs on a modifier-click
(Ctrl on PC/Linux, ⌘ on Mac). The string form (snapshot: "high") still works. - Clipboard uses the async Clipboard API (needs a secure context — HTTPS/localhost;
the click is the required user gesture). - New export
copyPng(blob); newSnapshotTarget/SnapshotDeliverytypes;
ToolbarItem.onClicknow receives theMouseEvent(for modifier keys).