Declare an element
One static Element<T> on the content class says what the element contains, which attributes and traits it has, and which commands it handles. Every first-party element is declared this way.
dart
import 'package:vyuh_document/element.dart';A complete declaration
The divider, from vyuh_document_extension_divider:
dart
final class Divider extends Block {
Divider({double thickness = 1})
: _node = divider.DividerBlock(
id: freshNodeId(),
format: divider.DividerFormat(thickness: thickness),
);
Divider._(this._node);
final divider.DividerBlock _node;
@override
Element<Node> get declaration => Divider.element;
static final thickness = Number<Divider>(
name: 'thickness',
min: 0.25,
max: 12,
read: (d) => d._node.format.thickness,
write: (d, value) => Divider._(
d._node.copyWith(format: d._node.format.copyWith(thickness: value.toDouble())),
),
);
static final element = Element<Divider>.block(
name: const ElementName('vyuh.document.block.divider'),
contains: const Nothing(),
attributes: [thickness],
traits: const [Atom(), KeepTogether()],
bridge: Bridge<Divider>(
wrap: (node) => Divider._(node as divider.DividerBlock),
unwrap: (d) => d._node,
definitions: divider.createDividerDocumentElementCollection,
),
);
}The parts
| Part | Options |
|---|---|
Element<T>.block(...) / .inline(...) | name (the only string, used in every format), contains, bridge, optional label, groups, attributes, traits, commands |
Content rule (contains) | Text(), Nothing(), Many(item, min:, max:), Optional(item), Sequence([...]), AnyOf(Groups.block) |
| Attributes | Choice<T, E> (an enum), Number<T> (with min/max), Flag<T>; each has name, read, write |
| Traits | SplitsAcrossPages, KeepTogether, Atom, Frame, Rows, Columns, OwnsStories(kind), Numbered(counter), PageAware |
Bridge<T> | Maps the typed class onto its canonical node: wrap, unwrap, definitions, and children / withChildren for containers |
A command family
An element's own commands form a sealed family with one exhaustive handler:
dart
sealed class CodeCommand<R> extends Command<R> {
const CodeCommand(this.code);
final Ref<Code> code;
}
final class SetLanguage extends CodeCommand<void> {
const SetLanguage(super.code, {this.language});
final String? language;
// name and toJson() come from the generated codec
}The element passes ElementCommands<CodeCommand>(handle: ..., codecs: ..., names: ...). The handler gets an Edit: read(ref) reads typed content, apply(...) stages changes, and freshId() and refTo(...) make new nodes. Everything staged becomes one validated transaction; a failed command changes nothing.
Generated JSON
vyuh_document_generator writes the command codecs, argument descriptions and name lists (which power didYouMean) into <library>.g.dart:
sh
dart run vyuh_document_generatorA command's serialized name is its class name; its summary is the first sentence of its doc comment.
The bridge maps the typed class onto the element's canonical definition (DocumentElementCollection), which Add a new element describes.
Next
- Build an extension: the editor runtime side.
- Element authoring reference.