Transactions, history and previews
Implementation contracts for custom engine integrations. Ordinary applications use the session commands and undo/redo API.
DiagramTransaction
Use a transaction when several related changes must publish and undo together.
dart
DiagramTransaction({
required List<DiagramStep> steps,
DrawingSelection? selectionAfter,
String label = 'Edit diagram',
})| Property | Type | Default | Contract |
|---|---|---|---|
steps | List<DiagramStep> | Required | Ordered, unmodifiable copy. Each step receives the preceding step's document. |
selectionAfter | DrawingSelection? | null | Optional resulting selection. Otherwise retain the current selection, then map it against the resolved result. |
label | String | 'Edit diagram' | Human-readable operation label retained for history and change events. |
dart
final decision = session.dispatch(DiagramTransaction(
label: 'Move pair',
steps: [
MoveShapeStep(
elementId: firstId,
delta: const DiagramPoint(40, 0),
),
MoveShapeStep(
elementId: secondId,
delta: const DiagramPoint(40, 0),
),
],
));
decision.requireAllowed();Steps are evaluated and the complete candidate is validated and resolved before publication. If a step throws, validation fails, or admission denies the candidate, the transaction does not partially publish. Direct transaction.apply(state) only computes a candidate; use session.dispatch to perform an edit with validation, history and notifications.
DiagramStep
dart
abstract interface class DiagramStep {
DiagramDocument apply(DiagramDocument document);
}Custom steps return a new immutable document. They must not mutate the input or call back into the store to publish another edit. The store applies its normal validation, lock checks and host admission to their result.
| Built-in step | Constructor arguments | Effect |
|---|---|---|
InsertElementStep | Positional DiagramElement element | Insert one element; rejects an existing ID. |
InsertElementsStep | Positional List<DiagramElement> elements | Insert a batch; rejects duplicate or existing IDs. |
RemoveElementStep | Positional String elementId | Remove one existing element. Prefer the removal command when connected or owned elements need cleanup. |
RemoveElementsStep | Positional Set<String> elementIds | Remove the canonical removal closure, including connectors that would retain dangling bindings. |
ReplaceElementStep | Required named DiagramElement before, DiagramElement after | Replace an unchanged expected element; requires the same ID. |
MoveShapeStep | Required named String elementId, DiagramPoint delta | Translate an existing framed element. |
ResizeShapeStep | Required named String elementId, DiagramRect frame | Replace a framed element's bounds; width and height must be positive. |
RotateShapeStep | Required named String elementId, double rotation | Set a framed element's rotation in radians. |
ReplaceTextStoryStep | Required named String elementId, TextStory before, TextStory after | Replace the expected story within its text owner. |
Stale before values throw StateError rather than overwriting newer edits. For complete document replacement and its revision check, use persistence and import.
Advanced: history groups
These methods belong to an explicitly owned headless DiagramStore, not the session surface. DrawingSession.dispatch does not accept a group token. Use one transaction for a synchronous batch; use a group when several successive publications belong to one operation.
| Method | Signature | Contract |
|---|---|---|
beginHistoryGroup | DiagramHistoryGroup beginHistoryGroup() | Close the previous group and obtain ownership of a new group. Requires no active interaction. |
dispatch | DiagramPolicyDecision dispatch(DiagramTransaction transaction, {DiagramHistoryGroup? historyGroup}) | With a current token, publish a provisional grouped edit; without one, close the group and dispatch an independent edit. |
ownsHistoryGroup | bool ownsHistoryGroup(DiagramHistoryGroup token) | Check whether the token still owns the group. |
endHistoryGroup | DiagramPolicyDecision endHistoryGroup(DiagramHistoryGroup token) | Admit the final change and close it. A stale token is an allowed no-op. |
cancelHistoryGroup | void cancelHistoryGroup(DiagramHistoryGroup token, {String label = 'Cancel grouped edit'}) | Restore the baseline and prior redo branch. A stale token is a no-op. The current implementation does not emit a cancellation event or use its label. |
DiagramHistoryGroup is an opaque token obtained from the store; it has no public constructor. Its read-only int operationId identifies this operation within the store lifetime. The first content-changing transaction supplies the retained group label.
An unrelated dispatch, undo/redo, new history group or pointer interaction ends ownership. Dispatching with an expired token throws StateError. Group ownership is not an exclusive lock across asynchronous host work: check ownership before continuing and submit a fresh intent when it has been superseded.
Advanced: interaction previews
Try the transactions and undo example for a session-owned multi-element preview, commit, cancellation and redo recovery. Each lease.preview(transaction) evaluates from the original edit baseline. An expired lease returns a denied decision for preview and commit; its cancel is a no-op. Check decisions with requireAllowed() or handle the reason in your UI.
Custom tools can use the same engine interaction boundary. Built-in canvas tools already manage this lifecycle. Retain the returned DiagramInteractionLease when callbacks can outlive their gesture; it prevents a delayed callback from modifying a newer interaction.
| Member | Signature | Contract |
|---|---|---|
isInteracting | bool get isInteracting | Whether an interaction owns the preview baseline. |
interactionStart | DiagramEditorState? get interactionStart | Immutable baseline, or null when idle. |
beginInteraction | DiagramInteractionLease beginInteraction(String label) | Close any history group, capture the baseline and return its ownership lease. Throws StateError if another interaction is active. |
previewInteraction | DiagramPolicyDecision previewInteraction(DiagramTransaction transaction) | Evaluate from the original baseline, not the previous preview. Requires an active interaction. |
commitInteraction | DiagramPolicyDecision commitInteraction() | Recheck admission and record one nonempty history entry. An idle call is an allowed no-op. |
cancelInteraction | void cancelInteraction() | Restore the baseline without a history entry. An idle call is a no-op. |
DiagramInteractionLease
The lease has no public constructor. Obtain it from store.beginInteraction. Prefer its scoped methods over unscoped store.previewInteraction and store.commitInteraction when implementing a custom tool or asynchronous host control.
| Member | Signature | Contract |
|---|---|---|
isActive | bool get isActive | Whether this lease still owns the live store interaction. |
preview | DiagramPolicyDecision preview(DiagramTransaction transaction) | Preview from this interaction's baseline. Returns denial when the lease is inactive. |
commit | DiagramPolicyDecision commit() | Commit this interaction. Returns denial when inactive, even if another interaction now exists. |
cancel | void cancel() | Cancel only this interaction; an inactive lease is a no-op. |
A lease becomes inactive after completion, cancellation or store disposal. Unlike the unscoped store methods, stale callbacks cannot affect a successor interaction. For scalar shape controls, the value-edit wrapper builds the transactions and owns this lease for you.
For cumulative movement, supply the total delta from gesture start on each preview. Applying an incremental pointer delta here would repeatedly restart from the baseline. Denied previews restore the baseline while retaining ownership; a later permitted preview can recover. Denied commits restore the baseline and end the interaction.