Create custom shapes
A custom type is a registry definition, not a new painter or a private drag implementation. Compose the available outline, text, port, and capability grammar; the shared runtime resolves its geometry and handles editing.
Declare a node
dart
import 'package:vyuh_diagram_kit/vyuh_diagram_kit.dart';
ShapeDefinition reviewNode() => ShapeDefinition(
type: 'example.review',
outline: const ShapeOutlineDefinition.rectangle(),
textBounds: const ShapeBoundsDefinition.inset(16),
minimumSize: const DiagramSize(140, 80),
defaultSize: const DiagramSize(220, 120),
defaultStyle: const ShapeStyle(cornerRadius: 12),
capabilities: ShapeCapabilities(
rotatable: false,
cornerRadiusEditable: true,
textSizing: ShapeTextSizing.growHeight,
),
connection: ShapeConnectionDefinition.fixedPorts([
ShapePortDefinition.onSide(
id: 'input',
side: DiagramPortSide.left,
),
ShapePortDefinition.onSide(
id: 'output',
side: DiagramPortSide.right,
),
]),
);
ShapeRegistry reviewRegistry() => ShapeRegistry.withStandard([
reviewNode(),
]);Pass reviewRegistry() into the session's shapeRegistry. Before or after mounting, create an instance using the registered type:
dart
String insertReview(DrawingSession session) =>
session.createShape(
type: 'example.review',
worldCenter: const DiagramPoint(320, 240),
);The creation command uses definition defaults and the canonical text-story factory. The instance participates in shared selection, text editing, history and routing.
Keep one definition per type
ShapeRegistry.withStandard(custom) adds unique types. Duplicate definitions are rejected. To intentionally replace a built-in definition, use the separate overrides argument with an existing type ID. To build an entirely curated library, construct ShapeRegistry(definitions) without standard shapes.
Attach capabilities
ShapeCapabilities declares selection, movement, deletion, resize sites, rotation, text editing, appearance editing, text growth and child ownership/clipping. These are definition-level behaviors; they do not yet provide a complete per-instance lock/unlock feature.
Text layers can declare named slots and title/body regions through ShapeTextLayerDefinition. Their content stays in the shared rich-text story. A title or body should not introduce a second text editor.
Embed paragraph content
A text region uses the same rich-text story and editor as a text box. Declare its bounds and slot; the kit owns input, selection, layout and undo. Several paragraph blocks can flow through one slot.
dart
ShapeTextLayerDefinition(
id: 'description',
slotId: 'description',
bounds: ShapeBoundsDefinition.inset(16),
verticalAlignment: DiagramTextVerticalAlignment.top,
emptyPlaceholder: 'Add a description…',
)Add the layer to your definition's layers. Multiline content is the default; singleLine: true is an explicit restriction for a short title region. Set ShapeCapabilities(textSizing: ShapeTextSizing.growHeight) when the shape should grow to accommodate content. Fixed-size regions retain their declared bounds.
Text boxes created with a click start in automatic-width mode: Enter adds a new paragraph, while the box grows horizontally as you type. Drag to create a wrapping box, or resize its width afterward. Its height grows to fit the paragraphs.
Paragraphs retain canonical runs, marks, list kind, nesting and alignment. Shapes and connector labels share story ownership, layout and editing commands; a host should not add an independent text field with a second copy of the content.
Connections can be disabled, outline-bound, fixed-port, default-side-port, or instance-port based. ShapePortLayout.distribute positions multiple ports on a side using the same resolver-owned placement contract.
Keep domain meaning above geometry
The names input and output in this example are stable port IDs. They do not automatically enforce workflow direction or payload compatibility. A domain package declares those meanings and supplies a session connectionPolicy to enforce them.
Custom type IDs are open. Geometry productions are deliberately finite: rectangle, ellipse, regular polygon and authored path. Adding a genuinely new geometry or interaction primitive requires extending its shared grammar and resolution contract, not bypassing it with shape-specific painting.
Arbitrary Flutter widget regions are not yet a first-class shape capability. See widget support and its boundaries.
See capability status before relying on embedded controls, port labels, or connector-label editing.
Shared text regions
Every text-capable shape uses the same story, block, run, and resolved-line pipeline. Define separate text regions for a card title and body rather than mounting private text editors.
A single-line region accepts one paragraph with inline formatting. Enter finishes editing that region. Multiline regions support paragraph blocks, ordered and unordered list items, nested lists, and checklist items. Typing [] or [ ] at the beginning of a multiline paragraph creates a checklist item.
The default line-height multiplier is 1.2. Lines reflow within the region width after accounting for list indentation. Empty paragraphs still resolve a full line and caret. The declared text sizing policy controls whether the owner grows or retains its bounds; rendering and editing consume the same line geometry.
Checklist markers use resolved geometry for painting and clicking. Clicking a checkbox toggles its canonical block state, preserves text selection, and can be undone.
Paragraph alignment applies only to ordinary paragraphs. Bulleted lists, numbered lists, and checklists remain left-aligned in their region, including nested items. Markers reserve their measured width plus a half-em gap before the item text.
Standalone text
Use TextElement for text that does not belong to a shape. Configure its regions and interaction behavior through the registry's text: TextElementDefinition(...) argument. It shares FramedElement transforms and the same rich-text story machinery as cards, but exposes no shape style, ports, image, or child ownership. The reserved text type is not a custom shape definition. See the text API for an insertion example.