A rich-text field
You will build a comment box: paragraphs with bold, italic, underline, strikethrough, superscript, subscript and links, sized like a text field, with a format bubble over the selection.
Packages
yaml
dependencies:
vyuh_document:
hosted: https://pub.vyuh.tech
version: ^0.19.3
vyuh_document_editor:
hosted: https://pub.vyuh.tech
version: ^0.19.3sh
flutter pub add vyuh_document --hosted-url https://pub.vyuh.tech
flutter pub add vyuh_document_editor --hosted-url https://pub.vyuh.techThe code
dart
import 'package:flutter/material.dart';
import 'package:vyuh_document/vyuh_document.dart'
show DocumentConfiguration, DocumentController;
import 'package:vyuh_document_editor/document_editor.dart';
void main() => runApp(const MaterialApp(home: Scaffold(body: CommentBox())));
/// A rich-text field: paragraphs, bold, italic, underline, strike and links.
class CommentBox extends StatefulWidget {
const CommentBox({super.key});
@override
State<CommentBox> createState() => _CommentBoxState();
}
class _CommentBoxState extends State<CommentBox> {
// One controller per editing session. Create it once, close it on dispose.
final controller = DocumentController(DocumentConfiguration.richText);
@override
void dispose() {
controller.close();
super.dispose();
}
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(24),
child: DocumentEditor(
controller: controller,
// Grows from 3 to 8 lines, then scrolls.
surface: const DocumentSurface.field(minLines: 3, maxLines: 8),
// A format bubble appears over a text selection.
chrome: EditorChrome.floating,
autofocus: true,
),
);
}Run it and type. Select a word: a bubble offers the marks and link. Command/Ctrl + Z undoes, Shift + Command/Ctrl + Z redoes, and Command/Ctrl + K opens the link dialog.
What each piece does
| Piece | Role |
|---|---|
DocumentConfiguration.richText | What content is allowed: paragraphs and the six marks. Anything else is refused or degraded. |
DocumentController | One session: the document, a selection, an undo history. Pure Dart. |
DocumentSurface.field(minLines:, maxLines:) | Where it shows: a field that grows, then scrolls. |
EditorChrome.floating | Which controls: a format bubble over a selection. |
Swap any one without touching the others.
A one-line input
DocumentSurface.line(onSubmit: send) turns Enter into a submit and pastes line breaks as spaces. Use it for a chat composer.
Read what the user wrote
dart
final text = controller.document.plainText; // one line per block
final json = controller.document.toJson(); // the full canonical JSONStore the JSON; reopen it with Document.fromJson(DocumentConfiguration.richText, json), which returns a Result instead of throwing on bad input.
The same field on the editor runtime
Later steps use DocumentEditorController. Here is the same paragraph-only note written with it, in a 320 × 160 box with five toolbar actions:
note.dart
dart
import 'package:material_ui/material_ui.dart';
import 'package:vyuh_document_editor/vyuh_document_editor.dart';
void main() => runApp(
MaterialApp(
theme: DocumentEditorTheme.light().materialTheme,
darkTheme: DocumentEditorTheme.dark().materialTheme,
home: const RichNote(),
),
);
class RichNote extends StatefulWidget {
const RichNote({super.key});
@override
State<RichNote> createState() => _RichNoteState();
}
class _RichNoteState extends State<RichNote> {
late final controller = DocumentEditorController.richText(
layoutMode: DocumentEditorLayoutMode.pageless,
document: Document.empty(title: 'A note'),
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: SizedBox(
width: 320,
height: 160,
child: DocumentEditor(
controller: controller,
configuration: DocumentEditorConfiguration.richText(
toolbar: DocumentToolbarConfiguration(
actionIds: {
HistoryActionId.undo,
HistoryActionId.redo,
MarkActionId.bold,
MarkActionId.italic,
MarkActionId.underline,
},
),
),
),
),
),
);
}Next
Step 2: toolbar and structure adds headings, lists and tables.