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.
| Callback | Signature | Purpose |
|---|---|---|
beforeChange | DiagramPolicyDecision Function(DiagramChangeProposal) | Admit or reject a validated candidate before publication. |
onChange | void Function(DiagramDocumentChange) | Observe a completed, nonempty document edit. |
onShapeRegionInteraction | bool Function(ShapeRegionInteraction) | Handle input on a declared shape region. |
onConnectionEvent | void 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.
| Property | Type | Meaning |
|---|---|---|
before | DiagramDocument | Operation baseline, not the previous pointer or IME preview. |
candidate | DiagramPublication | Validated proposed state and resolved scene. |
current | DiagramPublication | Currently published state and scene. |
after | DiagramDocument | candidate.state.document. |
phase | DiagramChangePhase | preview or commit. |
origin | DiagramDocumentChangeOrigin | Entry path listed below. |
operationId | int | Store-local identity retained across previews and final commit. |
label | String | Operation label. |
elements | DiagramElementChangeSet | Lazy baseline-to-candidate delta; computed only when read. |
DiagramPolicyDecision
| Member | Arguments | Result |
|---|---|---|
const DiagramPolicyDecision.allow() | None | allowed == true, reason == null. |
const DiagramPolicyDecision.deny([String? reason]) | Optional explanation | allowed == false; retains the explanation. |
bool allowed | Read-only property | Whether admission succeeded. |
String? reason | Read-only property | Optional denial explanation. |
void requireAllowed() | None | Returns 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.
| Property | Type | Meaning |
|---|---|---|
before | DiagramDocument | Baseline before the completed edit. |
publication | DiagramPublication | Published state and resolved scene after the edit. |
after | DiagramDocument | publication.state.document. |
label | String | Completed operation label. |
origin | DiagramDocumentChangeOrigin | transaction, import, interaction, groupedEdit, undo, or redo. |
operationId | int | Matches the admission proposal for this operation. |
elements | DiagramElementChangeSet | Lazy baseline-to-final delta. |
DiagramElementChangeSet
| Property | Type | Meaning |
|---|---|---|
insertedIds | Set<String> | Identities added to the document. |
removedIds | Set<String> | Identities removed from the document. |
updatedIds | Set<String> | Existing identities whose elements changed. |
orderChanged | bool | Element 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 path | Admission behavior |
|---|---|
dispatch | Checks the proposed transaction before history or publication changes. |
| Gesture preview | Checks baseline-to-candidate with preview phase. Denial restores the baseline while retaining gesture ownership for recovery. |
| Gesture commit | Rechecks with commit phase. Denial restores the baseline and closes the gesture without a history entry. |
| Grouped text editing | Checks provisional changes, then checks the complete operation when the group closes. Final denial restores the group baseline and prior redo branch. |
| Snapshot import | Checks the complete replacement and expected revision through the same transaction pipeline. |
| Undo and redo | Checks the actual restoration before advancing history. |
| Cancellation | Always restores the baseline, without consulting admission. |
| Camera, selection, grids and layout refresh | Remain 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
| Property | Type | Meaning |
|---|---|---|
elementId | String | Shape receiving input. |
regionId | String | Declared region ID. |
interactionId | String | Declared interaction ID. |
phase | ShapeRegionInteractionPhase | start, update, end, or cancel. |
worldPoint | DiagramPoint | Pointer position in world coordinates. |
localPoint | DiagramPoint | Pointer 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
| Property | Type | Default / meaning |
|---|---|---|
phase | DiagramConnectionEventPhase | Required: portHoverEnter, portHoverExit, started, targetChanged, committed, or canceled. |
connectorId | String? | null when unavailable. |
source | ConnectorEndpoint? | Source endpoint, when available. |
target | BoundEndpoint? | Bound target, when available. |
decision | DiagramPolicyDecision | Defaults 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.