Skip to content

Build your first shape library ​

Follow this path: first canvas → this library recipe → composition rules → verbs. Use the common tasks page for command outcomes and persistence; open the API reference when you need individual options.

This complete example imports vyuh_diagram_flutter, its extension authoring library package:vyuh_diagram_flutter/extension_api.dart (shape definitions, verbs, tool definitions and key bindings) and the editor, vyuh_diagram_extension_editor. Paste it into lib/main.dart after installing both. The shape has a fixed-height title and a flexible body. The button and a shortcut perform the card's own setRevision verb, which 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:flutter/services.dart';
import 'package:vyuh_diagram_extension_editor/vyuh_diagram_extension_editor.dart';
import 'package:vyuh_diagram_flutter/extension_api.dart';
import 'package:vyuh_diagram_flutter/vyuh_diagram_flutter.dart';

final reviewLibrary = ShapeLibrary(
  id: 'review',
  name: 'Review shapes',
  shapes: [
    ShapeDefinition(
      type: 'review.card',
      name: 'Review',
      size: const DiagramSize(240, 140),
      verbs: [setRevision],
      values: {
        'revision': const NumberValueField(
          defaultValue: 0,
          minimum: 0,
          integer: true,
        ),
      },
      body: ShapeColumn([
        ShapeSized(
          extent: 36,
          child: const ShapeText(
            'title',
            text: 'Review',
            textType: PlainTextDefinition(maxLines: 1),
          ),
        ),
        const ShapeText('body', text: 'Describe the review'),
      ]),
    ),
  ],
);

/// Sets a card's revision; each revision widens the card by 20. Performing it
/// with the previous revision restores both.
final setRevision = DiagramVerb.update<ShapeElement>(
  id: 'setRevision',
  label: 'Revision',
  target: DiagramVerbTarget.element,
  parameters: [DiagramVerbParameter.integer('revision', minimum: 0)],
  update: (card, call) {
    final revision = call.arg<int>('revision');
    final frame = card.frame;
    return card.copyWith(
      values: {...card.values, 'revision': revision},
      frame: DiagramRect.fromLTWH(
        frame.left,
        frame.top,
        frame.width + 20 * (revision - revisionOf(card)),
        frame.height,
      ),
    );
  },
);

int revisionOf(DiagramElement? card) => switch (card) {
  ShapeElement(:final values) => (values['revision'] as num? ?? 0).toInt(),
  _ => 0,
};

/// The portable half: shapes with a palette tool each.
final reviewExtension = DiagramExtension(
  id: 'app.review',
  shapes: reviewLibrary.shapes,
  tools: DiagramToolDefinition.forShapes(reviewLibrary.shapes, group: 'review'),
);

/// The Flutter half: a keyboard shortcut for the same verb. It installs the
/// portable half as its dependency.
final reviewShortcuts = DiagramFlutterExtension(
  id: 'app.review_keys',
  dependencies: [reviewExtension],
  shortcuts: [
    DiagramKeyBinding(
      verb: 'setRevision',
      args: (controller, id) => {
        'revision': revisionOf(controller.elementById(id ?? '')) + 1,
      },
      key: LogicalKeyboardKey.keyR,
      modifiers: const {DiagramKeyModifier.command, DiagramKeyModifier.shift},
      scope: DiagramKeyScope.selection,
      shapeType: 'review.card',
    ),
  ],
);

DiagramController createReviewController() => DiagramController(
  configuration: DiagramConfiguration(
    extensions: [EditorExtension(), reviewShortcuts],
  ),
);

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 controller = createReviewController();
  late final String card = controller.createShape('review.card', at: const DiagramPoint(300, 200));

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    body: Column(
      children: [
        TextButton(
          onPressed: () => controller.perform(
                [card],
                'setRevision',
                {'revision': revisionOf(controller.elementById(card)) + 1},
              ),
          child: const Text('Revise and widen'),
        ),
        Expanded(child: DiagramCanvas(controller: controller)),
      ],
    ),
  );
}

The declared revision value is persisted data; it is not automatically rendered as text. Use embedded controls to display domain values, or use controller.edits.setText(id, value, slot: 'body') 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 its verbs registered when reopening saved JSON. Libraries define shape types; the registry combines the chosen libraries and validates IDs. Documents store instances and content, not verbs or other 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 ​

  1. Add a card and edit its title and body independently.
  2. Activate Revise and widen; confirm one Undo restores both value and width. controller.edits.perform([card], 'setRevision', {'revision': 0}) does the same as a forward edit.
  3. Save with controller.save(DiagramJsonCodec.new) and reopen with the same extensions.
  4. Try a locked target: the verb returns a denied result with DiagramDenialCode.locked; handle it, or DiagramChangeDenied from requireAllowed(), at your UI boundary.
  5. Unmount and dispose the controller.

The repository's executable recipe check verifies verbs, 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.