Skip to content

Events and hooks

Configure host integration in DrawingConfiguration.hooks. The session installs document admission on its canonical store, so a mounted canvas, headless session, custom tool and direct transaction use the same boundary.

DrawingHooks

dart
const DrawingHooks({
  DiagramBeforeChange? beforeChange,
  DiagramDocumentChangeListener? onChange,
  ShapeRegionInteractionHandler? onShapeRegionInteraction,
  DiagramConnectionEventHandler? onConnectionEvent,
})

All four properties are final and default to null.

CallbackSignaturePurpose
beforeChangeDiagramPolicyDecision Function(DiagramChangeProposal)Admit or reject a validated candidate before publication.
onChangevoid Function(DiagramDocumentChange)Observe a completed, nonempty document edit.
onShapeRegionInteractionbool Function(ShapeRegionInteraction)Handle input on a declared shape region.
onConnectionEventvoid Function(DiagramConnectionEvent)Observe the UI connection lifecycle.

Example

dart
final session = DrawingSession(
  document: document,
  configuration: DrawingConfiguration(
    hooks: DrawingHooks(
      beforeChange: (proposal) {
        if (readOnly) {
          return const DiagramPolicyDecision.deny('This drawing is read-only.');
        }
        return const DiagramPolicyDecision.allow();
      },
      onChange: (change) {
        // Queue persistence or host work using this immutable snapshot.
        saveQueue.add(change.after);
      },
    ),
  ),
);

DiagramChangeProposal

The argument to beforeChange. All constructor arguments are required; after and elements are derived.

PropertyTypeMeaning
beforeDiagramDocumentOperation baseline, not the previous pointer or IME preview.
candidateDiagramPublicationValidated proposed state and resolved scene.
currentDiagramPublicationCurrently published state and scene.
afterDiagramDocumentcandidate.state.document.
phaseDiagramChangePhasepreview or commit.
originDiagramDocumentChangeOriginEntry path listed below.
operationIdintStore-local identity retained across previews and final commit.
labelStringOperation label.
elementsDiagramElementChangeSetLazy baseline-to-candidate delta; computed only when read.

DiagramPolicyDecision

MemberArgumentsResult
const DiagramPolicyDecision.allow()Noneallowed == true, reason == null.
const DiagramPolicyDecision.deny([String? reason])Optional explanationallowed == false; retains the explanation.
bool allowedRead-only propertyWhether admission succeeded.
String? reasonRead-only propertyOptional denial explanation.
void requireAllowed()NoneReturns normally when allowed; otherwise throws DiagramChangeDenied.

DiagramChangeDenied.decision contains the rejected decision. Its string representation uses the reason when supplied.

DiagramDocumentChange

The argument to onChange. All constructor arguments are required; after and elements are derived.

PropertyTypeMeaning
beforeDiagramDocumentBaseline before the completed edit.
publicationDiagramPublicationPublished state and resolved scene after the edit.
afterDiagramDocumentpublication.state.document.
labelStringCompleted operation label.
originDiagramDocumentChangeOrigintransaction, import, interaction, groupedEdit, undo, or redo.
operationIdintMatches the admission proposal for this operation.
elementsDiagramElementChangeSetLazy baseline-to-final delta.

DiagramElementChangeSet

PropertyTypeMeaning
insertedIdsSet<String>Identities added to the document.
removedIdsSet<String>Identities removed from the document.
updatedIdsSet<String>Existing identities whose elements changed.
orderChangedboolElement order changed, including insertion or removal.

Sets are unmodifiable. Computing the delta scans the documents in O(n). Replacing the document identity treats all old elements as removed and all new elements as inserted, even when local element IDs match.

Document admission

beforeChange receives a DiagramChangeProposal containing the operation's before document, validated candidate publication, currently published snapshot, label, origin, phase and operation ID. Its after document and lazy elements delta describe the actual candidate, including custom steps and indirectly removed elements.

Return DiagramPolicyDecision.allow() or DiagramPolicyDecision.deny(reason). The hook cannot rewrite the candidate or synchronously mutate the session. Transformations belong in explicit transactions. An exception aborts admission and propagates to the caller; it never becomes an implicit allow.

Entry pathAdmission behavior
dispatchChecks the proposed transaction before history or publication changes.
Gesture previewChecks baseline-to-candidate with preview phase. Denial restores the baseline while retaining gesture ownership for recovery.
Gesture commitRechecks with commit phase. Denial restores the baseline and closes the gesture without a history entry.
Grouped text editingChecks provisional changes, then checks the complete operation when the group closes. Final denial restores the group baseline and prior redo branch.
Snapshot importChecks the complete replacement and expected revision through the same transaction pipeline.
Undo and redoChecks the actual restoration before advancing history.
CancellationAlways restores the baseline, without consulting admission.
Camera, selection, grids and layout refreshRemain available without document permission.

Low-level session.dispatch, undo and redo return a DiagramPolicyDecision. Convenience creation commands return an identity on success and throw DiagramChangeDenied on denial; they never return an ID for content that was not inserted.

Operation IDs are local to a store. A gesture or composition retains its ID across previews and final admission. Reading the optional element delta currently computes an O(n) document diff; receiving a proposal alone does not allocate that diff.

Completed changes and live state

Use session.addListener for live state, including interaction previews and provisional text composition. Use hooks.onChange for completed transactions, committed gestures, grouped edits, undo and redo. Cancellation does not emit a completed change.

change.operationId matches the admission proposal for that operation. Grouped edits and gesture previews keep the same ID through their final commit. Undo and redo each receive a new ID. IDs are local to the store lifetime, not persistent document IDs or globally unique audit identifiers; a denied proposal has no corresponding completed change.

A grouped edit closes when composition finishes or an unrelated edit takes ownership. Its event contains the complete baseline-to-final delta. If final admission is denied, the unrelated operation is also left unapplied; submit a fresh intent against the restored state.

Observers cannot synchronously mutate the store. Queue later work as a separate command. All observers receive the publication before callback failures are forwarded to the current error zone.

Interaction callbacks

hooks.onConnectionEvent observes connection lifecycle events. hooks.onShapeRegionInteraction observes declared region interactions. These events describe UI activity; they do not replace document admission and are not synthesized for headless commands.

ShapeRegionInteraction

PropertyTypeMeaning
elementIdStringShape receiving input.
regionIdStringDeclared region ID.
interactionIdStringDeclared interaction ID.
phaseShapeRegionInteractionPhasestart, update, end, or cancel.
worldPointDiagramPointPointer position in world coordinates.
localPointDiagramPointPointer position in shape-local coordinates.

Return true at start to claim the interaction. Returning false permits fallback handling. Return values for subsequent phases are ignored.

DiagramConnectionEvent

PropertyTypeDefault / meaning
phaseDiagramConnectionEventPhaseRequired: portHoverEnter, portHoverExit, started, targetChanged, committed, or canceled.
connectorIdString?null when unavailable.
sourceConnectorEndpoint?Source endpoint, when available.
targetBoundEndpoint?Bound target, when available.
decisionDiagramPolicyDecisionDefaults to allow(); describes the associated policy decision.

The callback returns void. Observing an event does not create or authorize a connector.

The existing session shapeCreationPolicy and connectionPolicy callbacks still filter their respective service intents. They are not universal permission gates: custom transactions bypass those service filters but always pass through beforeChange. Put application-wide mutation rules in beforeChange.

Ownership and lifetime

Replace hooks through session.setConfiguration(session.configuration.copyWith(hooks: ...)). The next preview and final commit use the new callback. Replacement is prohibited during candidate evaluation or observer delivery.

A session created with DrawingSession.fromStore borrows the store's admission authority. It can add its own observation callbacks but cannot replace an existing store hook with a different one. Configure admission on the owning store. Disposing a session removes its observers; a retained store keeps its admission callback.

Initial document loading is validated but does not replay thousands of creation events. Authorizing access to a snapshot belongs to the host. Hooks are synchronous client-side controls; asynchronous approval and server authorization remain host responsibilities. See persistence and imports for revision-checked replacement. Broader interaction-event consolidation remains unfinished.