Documentation

Pro handbook

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.

See it on a map.