Build your first shape library
Follow this path: first canvas → this library recipe → composition rules → actions. Use the common tasks page for command outcomes and persistence; open the API reference when you need individual options.
This complete example uses only drawing.dart. Paste it into lib/main.dart after installing the kit. The shape has a fixed-height title and a flexible body. The button invokes a named action; the action updates declared data and width together in one Undo step. Editing body text uses the normal text editor.
dart
import 'package:flutter/material.dart';
import 'package:vyuh_diagram_kit/drawing.dart';
final reviewLibrary = ShapeLibrary(
id: 'review',
name: 'Review shapes',
shapes: [
ShapeDefinition(
type: 'review.card',
displayName: 'Review',
defaultSize: const DiagramSize(240, 140),
valueFields: {
'revision': const NumberValueField(defaultValue: 0, minimum: 0, integer: true),
},
composition: ShapeColumn([
ShapeSized(extent: 36, child: const ShapeText('title', text: 'Review', singleLine: true)),
const ShapeText('body', text: 'Describe the review'),
]),
),
],
);
DrawingSession createReviewSession() => DrawingSession(
shapeRegistry: ShapeRegistry.fromLibraries([
ShapeLibraries.standard,
reviewLibrary,
]),
configuration: DrawingConfiguration(
actions: [
DrawingAction(
id: 'review.revise',
isEnabled: (context) => context.shape?.type == 'review.card',
execute: (context) {
context.updateValues({
'revision': (context.shape!.values['revision'] as num) + 1,
});
context.resize(width: context.shape!.frame.width + 20);
},
),
],
keyBindings: const [
DrawingKeyBinding(
action: 'review.revise',
key: LogicalKeyboardKey.keyR,
command: true,
shift: true,
scope: DrawingActionScope.selection,
shapeType: 'review.card',
),
],
),
);
void main() => runApp(const MaterialApp(home: ReviewCanvas()));
class ReviewCanvas extends StatefulWidget {
const ReviewCanvas({super.key});
@override
State<ReviewCanvas> createState() => _ReviewCanvasState();
}
class _ReviewCanvasState extends State<ReviewCanvas> {
late final session = createReviewSession();
late final String card;
@override
void initState() {
super.initState();
card = session.createShape(
type: 'review.card', worldCenter: const DiagramPoint(300, 200),
);
}
@override
void dispose() {
session.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
body: Column(children: [
TextButton(
onPressed: () => session.invokeAction('review.revise', elementId: card).requireAllowed(),
child: const Text('Revise and widen'),
),
Expanded(child: DrawingCanvas(session: session)),
]),
);
}The declared revision value is persisted data; it is not automatically rendered as text. Use embedded controls to display domain values, or use session.text.setPlainText(elementId: id, slotId: 'body', text: value) for an explicit text edit. Plain-text editing does not require manipulating paragraphs, run offsets or selections. That separate command is its own Undo operation.
Keep the same library and actions registered when reopening saved JSON. Libraries define shape types; the registry combines the chosen libraries and validates IDs. Documents store instances and content, not executable callbacks. For a separate package, export your ShapeLibrary from its public Dart entry point; depend on layout/types for portable declarations, and keep Flutter controls in the host.
Check your integration
- Add a card and edit its title and body independently.
- Activate Revise and widen; confirm one Undo restores both value and width.
- Save with
session.document.encode()and reopen using the same registry. - Try a locked target and handle
DiagramChangeDeniedat your UI boundary. - Unmount and dispose the session without retaining action contexts.
The repository's executable recipe check verifies actions, Undo, persistence and denials. It is not evidence that an unfamiliar developer has completed onboarding. Hosted installation and observed novice onboarding remain separate release checks.