Skip to content

Shapes, elements and registries

A definition describes a reusable shape type. An element is one immutable record in the document. The registry connects its type to the definition. The session inserts and changes records through the canonical engine.

TypePackageRole
DiagramElementvyuh_diagram_typesBase authored record with identity, stacking and lock state.
ShapeElementvyuh_diagram_typesA shape instance: type, frame, appearance and content.
TextElementvyuh_diagram_typesStandalone text; shared rectangular mechanics without shape appearance.
ConnectorElementvyuh_diagram_typesConnection endpoints, route, markers and labels.
ShapeDefinitionvyuh_diagram_layoutReusable geometry, content slots, ports and interaction grammar.
ShapeRegistryvyuh_diagram_layoutDefinition lookup and validation.
DrawingSessionvyuh_diagram_kitHost commands, state, configuration and lifetime.

Import package:vyuh_diagram_kit/vyuh_diagram_kit.dart in a Flutter host; it exports these types. Pure Dart hosts can import the types, layout and engine packages directly.

session.addElement

Use an element directly when you already know its identity, frame and content:

dart
DiagramPolicyDecision addElement(
  DiagramElement element, {
  bool select = true,
})

final decision = session.addElement(ShapeElement(
  id: 'review-card',
  type: BuiltInShapeType.card,
  frame: const DiagramRect.fromLTWH(200, 160, 240, 160),
));
Argument / resultContract
elementRequired immutable record. Shapes, text and connectors use this same method.
selectDefaults to true; selects the inserted record. false preserves selection.
ResultDiagramPolicyDecision; denied changes return a reason. Call requireAllowed() to throw on denial.
DefaultsPreserves the supplied record; does not invent an ID, story, style, size or parent.
ValidationRegistry, identity, ownership, bindings and the store's beforeChange admission still apply. Invalid records throw; no partial insertion occurs.
HistoryOne admitted transaction and undo entry. Works before mounting a canvas.

The factory-specific shapeCreationPolicy and connectionPolicy are not called by raw insertion. Put permissions that must cover every mutation in the canonical beforeChange hook, which also covers transactions, imports and history.

session.createShape

Use named arguments when the registry should supply defaults. A host no longer needs to wrap the call in DiagramShapeCreation.

dart
final cardId = session.createShape(
  type: BuiltInShapeType.card,
  worldCenter: const DiagramPoint(320, 240),
  size: const DiagramSize(240, 160),
);

Returns String, the created element ID. These are all named arguments:

ArgumentTypeDefault / behavior
typeStringRequired registered shape type. Standalone text uses createText.
worldCenterDiagramPointRequired center in world coordinates.
idString?Allocated when omitted; explicit IDs must be unique.
sizeDiagramSize?Definition's defaultSize; requested dimensions are clamped up to its minimum.
styleShapeStyle?Definition's defaultStyle.
textStoryDiagramTextStory?Materialized from the definition's declared text slots when omitted.
textSizingModeDiagramTextSizingModefixedWidth; autoWidth opts into intrinsic text width.
portsList<DiagramPortInstance>Empty; admitted by the registered connection grammar.
normalizedPathList<DiagramPoint>Empty; normalized authored-path geometry when supported.
pathWidthsList<double>Empty; per-point pressure multipliers when supported.
polygonSidesint?Omitted; only applicable to a supporting polygon definition.
imageSourceDiagramImageSource?Omitted; only applicable to a supporting image definition.
parentDiagramShapeParentPlacementconst AutoDiagramShapeParent(): deepest accepting owner beneath the center.
selectbooltrue: select the new shape.
zIndexint0.

Use const RootDiagramShapeParent() for explicit root placement, or OwnedDiagramShapeParent(ownerId) for an explicit child-owning shape. Missing or unsuitable explicit owners throw. Registry admission, shapeCreationPolicy and beforeChange run before successful publication. Denial throws rather than returning a successful ID. The operation creates one undo entry and requires no mounted canvas.

session.createText

dart
final textId = session.createText(
  worldCenter: const DiagramPoint(320, 240),
  textStory: DiagramTextStory.paragraph(
    id: 'notes-story', blockId: 'notes-body', text: 'Your notes',
  ),
);

Returns the new String ID. Named arguments are worldCenter (required), id, size, textStory, textSizingMode, parent, select, and zIndex, with the same types/defaults as above. Omitted size and story come from ShapeRegistry.text. There are no fill, border, port, image or path arguments.

The engine still uses DiagramShapeCreation as an advanced factory/preview request, including in shapeCreationPolicy and atomic createConnectedShape. That request is not a document element and is not persisted. Ordinary host insertion does not require it. This distinction is intentional; the advanced request naming remains a candidate for further consolidation.

ShapeElement

Constructor propertyTypeDefault / contract
id, typeStringRequired identity and registry type.
frameDiagramRectRequired authored frame.
rotationdouble0, radians around the frame center.
styleShapeStyleconst ShapeStyle(); raw records do not acquire definition defaults.
textStoryDiagramTextStory?null.
textSizingModeDiagramTextSizingModefixedWidth.
ports, normalizedPath, pathWidthsListsEmpty immutable snapshots; subject to the definition's grammar.
polygonSides, imageSourceint?, DiagramImageSource?null.
contentScrollOffsetdouble0; text content scroll intent.
parentIdString?null; explicit owner identity when present.
clipContentboolfalse; requires a child-clipping definition.
isLockedboolfalse; per-instance editing lock.
zIndexint0; stacking order.

Records are immutable. Use copyWith to construct a replacement, then submit it through a command or transaction. Geometry and story data are validated on admission.

ShapeDefinition

Constructor propertyTypeDefault / contract
typeStringRequired open registry key.
outlineShapeOutlineDefinitionRequired outline grammar.
textBoundsShapeBoundsDefinitionRequired text-region bounds grammar.
supportsTextbooltrue.
minimumSizeDiagramSize24 × 24.
defaultSizeDiagramSize160 × 100.
defaultStyleShapeStyleconst ShapeStyle().
shadowShapeShadowDefinition?null.
capabilitiesShapeCapabilities?ShapeCapabilities() when omitted.
layersList<ShapeLayerDefinition>Empty immutable snapshot; text slots, regions, dividers and visuals.
connectionShapeConnectionDefinitionconst ShapeConnectionDefinition.outline().

DiagramTextStory? createTextStory(String elementId) materializes the declared text slots and initial content. It returns null when text is unsupported or not editable. It does not reset an existing element's story.

ShapeRegistry

ConstructionBehavior
ShapeRegistry.standard(...)Built-in shape definitions.
ShapeRegistry.withStandard(definitions, overrides: ..., ...)Add unique definitions; intentionally replace named built-ins through overrides.
ShapeRegistry(definitions, ...)Curated set supplied by the host.

Duplicate IDs are rejected. Definitions are immutable. Register required types before opening a document; live replacement and migration of registered definitions remain unfinished. An already registered type can be instantiated at any time.

Shape registration does not install a toolbar button. DiagramToolRegistry separately declares creation tools, shortcuts and contributions. See custom shapes for a complete example.

Mutation and locking

OperationAPIBehavior
Lock / unlocksession.setElementsLocked(ids, locked)Returns DiagramPolicyDecision; one admitted reversible change.
Deletesession.removeElements(ids)Removes the ownership/dependency closure; locks, deletion capabilities and admission apply.
MoveMoveShapeStep(elementId: id, delta: ...)Relative framed-element translation.
ResizeResizeShapeStep(...)Framed-element resize; see transaction step arguments.
RotateRotateShapeStep(...)Framed-element rotation.
ReplaceReplaceElementStep(before: oldElement, after: newElement)Rejects a stale expected record.
Replace textReplaceTextStoryStep(...)Canonical story replacement.

Submit steps with session.dispatch(DiagramTransaction(steps: [...])); related steps publish and undo together. Raw steps are the advanced authoring interface. Capabilities and instance locks are editing declarations, not authorization for arbitrary host-authored transactions; use beforeChange for permissions.

Text elements and rectangular mechanics

TextElement is a distinct sibling of ShapeElement. Both extend FramedElement, so movement, resize, rotation, parent placement, selection, and history share the same mechanics. Text owns a required rich-text story; it has no shape style, fill, border, ports, image, path, or child-clipping fields.

Configure standalone text through ShapeRegistry.text, using the text argument on ShapeRegistry.standard, ShapeRegistry.withStandard, or the registry constructor. Do not register a ShapeDefinition with BuiltInShapeType.text or construct ShapeElement(type: BuiltInShapeType.text).

dart
final registry = ShapeRegistry.standard(
  text: TextElementDefinition(
    minimumSize: const DiagramSize(32, 24),
    defaultSize: const DiagramSize(200, 48),
  ),
);

TextElementDefinition declares text bounds, text layers, minimum/default sizes, and shared interaction capabilities. Its defaults grow text height and retain the selection outline during transforms. The definition rejects shape surfaces and child ownership/clipping. It does not introduce a second paragraph editor.

Text color belongs to DiagramTextRun.color. A null run color inherits the text region foreground. See standalone text for the record and persistence for legacy imports.

Transform geometry for custom commands

The pure Dart engine exposes geometry operations for custom commands and tools. They return immutable elements; they do not bypass publication, permissions, ownership closure, or history. Submit results through normal transactions.

APIArgumentsResult
DiagramElementTransform.translateDiagramElement element, DiagramPoint deltaTranslates a shape/text frame or connector free points.
DiagramElementTransform.rotateDiagramElement element, required DiagramPoint pivot, required double radiansRotates placement about a world-space pivot; framed element dimensions remain unchanged.
DiagramResizeTransformRequired DiagramRect fromBounds, DiagramRect toBounds; double rotation = 0Creates one immutable mapping for all elements in a resize selection. Bounds are expressed in its unrotated coordinate frame.
resize.mapPointDiagramPoint pointMaps a world point through the resize coordinate frame.
resize.applyDiagramElement element; optional DiagramRect resolvedFrame, bool fixTextWidth = falseMaps frame or free connector geometry. A horizontal text resize can set fixed wrapping width.

Use the resolved frame captured at the beginning of the gesture for auto-grown text. Use bounds from that same scene revision, and enforce registry minima when choosing toBounds. The mapping preserves text, styles, local paths, and bound endpoint identity. The resolver determines the final bound connector positions. Non-finite inputs and non-positive resize bounds are rejected.

Rounded outline geometry

Rounded rectangle radii fit to half the smaller frame dimension. The resolved outline uses those fitted arcs for containment and connector boundary anchors, as well as painting. A child clipped by a rounded parent cannot be hit through the parent's cut-off corners. Rotation is applied through the shared local/world transform; it does not change the local corner radius.

Content layer containment

Region fills, dividers and image visuals are clipped to their owning shape's resolved outline, including rounded corners and rotated outlines. Interactive regions use the same containment: clipped-away corners cannot activate a region. This is a rule of the layer grammar, independent of child-frame clipping.

Text continues to use ShapeCapabilities.clipTextToOutline. Ports and shadows are separate productions and can extend beyond the outline. On-screen drawing and PNG output share the scene painter and therefore the same content clip.