Events
editor.on(event, listener) subscribes and returns a function that unsubscribes. Listeners are
called synchronously, in the order they were added. editor.destroy() removes all of them.
The events#
| Event | Payload | When |
|---|---|---|
change |
{ label } |
An edit changed the partition. Also after every successful undo and redo, with the label of the step that was undone or redone. |
unchanged |
{ label } |
An edit went through without changing anything — for example an outline drawn exactly over existing geometry, or a click on a handle without moving it. |
rejected |
{ label, violations } |
An edit, undo or redo was refused; the partition is unchanged. See Validity and rejections. |
selectionchange |
{ selection } |
The selection's content changed (not on every assignment of the same content). selection is { faces?, edges?, nodes? } or undefined. |
modechange |
{ from, to } |
A mode was set; from is null for the first one. Mode names, not instances. |
ebenenchange |
{ aktiv } |
The layer list changed: active layer, order, visibility, lock, name, added or removed layer. aktiv is the active layer's ID. |
keyunhandled |
{ key, modifiers } |
A key the active mode did not consume. The toolbar listens here for its shortcuts. |
Every edit ends in exactly one of change, unchanged and rejected. Listen to all three if
you want to know what happened to a gesture; listening only to change cannot tell a swallowed
drawing from one that never finished.
The label is a stable English identifier such as 'draw-polygon' or 'merge-faces', never
translated — compare it in code, and map it to your own text for display. Modes
lists the labels per tool; 'undo' and 'redo' appear only in rejected. A custom mode reports
whatever label it passes to ctx.transaction.
Example#
import { formatMessage, type Violation } from '@vectorstream/core';
import type { Editor } from '@vectorstream/editor';
/** The text to show for a rejection: re-rendered from its code where it has one. */
export function describeViolation(v: Violation): string {
return v.code !== undefined ? formatMessage(v.code, v.params) : v.detail;
}
/** Logs every editor event; returns one function that unsubscribes them all. */
export function logEditorEvents(editor: Editor, log: (line: string) => void): () => void {
const off = [
// Exactly one of these three follows every transaction.
editor.on('change', ({ label }) => log(`change ${label}`)),
editor.on('unchanged', ({ label }) => log(`unchanged ${label}`)),
editor.on('rejected', ({ label, violations }) => {
log(`rejected ${label}: ${violations.map(describeViolation).join('; ')}`);
}),
// State changes that are not transactions.
editor.on('selectionchange', ({ selection }) => log(`selection ${JSON.stringify(selection ?? {})}`)),
editor.on('modechange', ({ from, to }) => log(`mode ${from ?? '-'} -> ${to}`)),
editor.on('ebenenchange', ({ aktiv }) => log(`layers changed, active ${aktiv}`)),
// Keys the active mode did not consume (the toolbar listens here for its shortcuts).
editor.on('keyunhandled', ({ key }) => log(`key ${key}`)),
];
return () => off.forEach((unsubscribe) => unsubscribe());
}
Its test draws a new triangle (change), the existing parcel again (unchanged) and three points
on a line (rejected), presses a key the draw tool ignores, selects with the lasso, and checks
that the returned function unsubscribes everything.
State for a status line#
Besides events, the editor answers questions about the current moment. These are read, not
subscribed to — call them after a pointer event or a change:
| Method / property | Returns |
|---|---|
editor.selection |
The current selection, or undefined. Assignable: editor.selection = { faces: [id] } (IDs that do not exist are dropped). |
editor.mode |
The active mode instance, or null. |
editor.aktion() |
What a click would do now, as { name, text, code? } — for example "click to close". null for tools without a hint. |
editor.messung() |
Measurement values of the current gesture (area, perimeter, segment length, position), or of the face under the pointer: a list of { name, label, wert }, already formatted with units. |
editor.messstelle() |
Where those values belong on the map (a world coordinate), or null. |
editor.history.canUndo(), canRedo() |
Whether undo and redo would do anything. |
A context menu#
adapter.on('contextmenu', e) reports a right click with its position (the adapter suppresses the
browser's own menu). With hit = adapter.hitTest(e.p, editor.tolerancePixel()),
editor.menueFuer(hit, e.c) returns the entries that apply there — each
with name, label, gruppe, aktiv, and optionally kuerzel (the matching shortcut, for
display), grund (why it is disabled), kinder (sub-entries) and ausfuehren() (an entry with
sub-entries executes nothing itself). It also selects the element under the pointer. Your
application draws the menu. See Menueintrag.
Adapter events#
The adapter's own events (adapter.on(…)): pointerdown, pointermove, pointerup,
contextmenu, key, pointercancel, pointerleave, viewchange. The editor subscribes to them
itself; you need them only for things outside the editor, such as positioning your own elements
after the view changed. See AdapterEvents.
API: EditorEvents, Violation.