API Overview
animated_streaming_markdown exposes parser APIs, render block models, and
Flutter renderer widgets from one public library:
import 'package:animated_streaming_markdown/animated_streaming_markdown.dart';
0.4.0 document extensions
The current repository also exposes MarkdownParser, MarkdownRenderer, and
MarkdownAnimator replacement contracts. MarkdownParseResult.document feeds
AnimatedStreamingMarkdown.fromDocument or
SliverAnimatedStreamingMarkdown.fromDocument. See
Custom Pipeline for extension points, ownership, and
an interactive example.
These additions coexist with the blocks APIs below. The pub.dev API reference tracks the published release and may not include 0.4.0 symbols until publication.
Parser
MarkdownStreamParser turns Markdown text into renderable blocks.
final parser = MarkdownStreamParser();
await parser.start();
final result = await parser.parse(
operation: MarkdownParseOperation.append,
text: chunk,
);
Prefer the convenience methods when the operation is static:
await parser.replace(markdown);
await parser.append(chunk);
For short complete Markdown snapshots, MarkdownSyncParser parses on the
current isolate:
final result = MarkdownSyncParser.parseMarkdown(
markdown,
backend: MarkdownSyncParserBackend.auto,
);
MarkdownSyncParserBackend.dart uses the pure-Dart parser path.
MarkdownSyncParserBackend.native uses the native parser when available and
falls back to the pure-Dart parser if the native library cannot be loaded.
MarkdownSyncParserBackend.auto prefers the native parser and falls back when
needed.
Use result.nativeAvailable and result.mode when diagnostics need to confirm
which parser path ran.
On Flutter web, warmUpStreamingMarkdownParser() attempts to load the optional
Tree-sitter WASM asset generated by tool/build_wasm.sh. If the asset is not
available, parser APIs fall back to the pure-Dart parser where possible.
Parse Result
MarkdownParseResult.blocks is the primary output for rendering:
final List<MarkdownBlock> blocks = result.blocks;
The result also exposes diagnostics such as parser mode, native availability, block counts, inline type counts, and timing values.
Renderer
AnimatedStreamingMarkdown renders parsed blocks:
AnimatedStreamingMarkdown(
blocks: result.blocks,
enableSelection: true,
);
For simple complete Markdown rendering, use the factory constructor:
AnimatedStreamingMarkdown.fromMarkdown(
markdown: markdown,
syncParserBackend: MarkdownSyncParserBackend.auto,
);
Important renderer options include:
blocksplaceholderasSlivertokenStaggerDelaytokenAnimationDurationtokenAnimationDurationFactortokenAnimationCurvetokenAnimationBuildertokenCompactiononTokenDelayonTokenAnimationEndshowCodeBlockCopyButtonenableSelectionselectionStrategyselectionControllerselectionScrollPaddingthemeblockBuilderimageBuilderlatexBuilder
Inline $...$ / \(...\) and display $$...$$ / \[...\] LaTeX expressions
render with the bundled KaTeX-compatible view renderer. Use latexBuilder
when you need to wrap or replace the default math widget.
Theming
Use AnimatedMarkdownThemeData to override renderer styling:
AnimatedStreamingMarkdown(
blocks: result.blocks,
theme: const AnimatedMarkdownThemeData(
blockSpacing: 16,
codeBlockBackgroundColor: Color(0xFF0F172A),
),
);
Custom Blocks
Use blockBuilder to replace or wrap a rendered block:
AnimatedStreamingMarkdown(
blocks: result.blocks,
blockBuilder: (context, block) {
if (block.block.type == 'thematic_break') {
return const Divider(thickness: 2);
}
return block.defaultWidget;
},
);
Return null to use the default widget.
Custom image and LaTeX builders remain inside the renderer's atomic selection
proxy automatically. If a block builder replaces the complete default widget
with a custom object, wrap it with AnimatedMarkdownSelectable and provide its
plain-text projection:
return AnimatedMarkdownSelectable(
plainText: block.block.content,
child: MyCustomObject(block.block),
);
This supplies geometry to the selection engine while raw and rich copy remain backed by the original Markdown source.
The default wrapper is atomic. Use AnimatedMarkdownSelectable.text when the
custom child is Text, RichText, or Flutter's SelectableText; selection is
then mapped character by character to Markdown source offsets. Composite custom
objects can use AnimatedMarkdownSelectable.fragments with
AnimatedMarkdownSelectionFragment around each text region. Interactive
non-text siblings remain enabled.
Incomplete Streaming Links
MarkdownBlock.inlineLinks exposes MarkdownInlineLink values with label,
the destination received so far, isCompleted, lossless source, and offsets.
The default renderer hides [Hel, shows the destination for
[Hello](https://hello as a tappable link, then shows the linked label after
) arrives.
This semantic scan covers direct inline links, including one unfinished link at the active source tail. Code spans, autolinks, images, and escaped opening brackets remain intentionally visible syntax and are not reclassified.
Use incompleteLinkTextBuilder to choose another temporary projection without
inspecting or rewriting the raw Markdown:
AnimatedStreamingMarkdown(
blocks: result.blocks,
incompleteLinkTextBuilder: (link) => link.label, // or return ''
);
Selection And Token Compaction
With enableSelection: true, the renderer maps pointer gestures to absolute
Markdown source ranges through selectable render proxies. The visual highlight
is projected from that stable range, so appends and scroll motion do not move
the chosen endpoints. Users can select partial text within table cells, drag
through a table into later blocks, and continue a drag at vertical or
horizontal scroll edges.
Selection is painted as a flat layer per visual line rather than as separate token backgrounds. Images and inline/display LaTeX are represented by their laid-out non-text bounds, so mouse selection, touch long-press, handles, and keyboard extension can cross formatted and non-text content consistently.
AnimatedMarkdownSelectionController.value exposes the current source
snapshot and directional TextSelection. Use selection, clear(), and
selectAll() for programmatic changes. selectedMarkdown returns the exact
source slice rather than reconstructing it from visible text.
Box mode creates a selection area internally. For asSliver: true, wrap the
CustomScrollView in AnimatedStreamingMarkdownSelectionArea and give the
wrapper and its single Markdown renderer the same controller. This allows
offscreen sliver children to unmount and rehydrate their local geometry when
they return.
tokenCompaction defaults to AnimatedMarkdownTokenCompaction.automatic.
After animated word tokens settle, it merges their animation hosts into lighter
static spans while retaining the same geometry for layout and selection.
Streaming State And Copy
append(chunk) updates the existing document incrementally. Existing settled
tokens and the source selection remain anchored while new blocks arrive; a
complete replacement should use replace(markdown). Applications that put
multiple renderers in a virtualized list should preserve each message's widget
identity and keep its parser/controller in state.
selectionStrategy.plain copies rendered plain text, raw copies the selected
Markdown source, and rich writes HTML together with the same plain-text
fallback. The rich writer is implemented for Web, Android, iOS, macOS,
Windows, and Linux. Native and programmatic writer failures retry with plain
text instead of surfacing UnsupportedError to the UI. Browser copy events
provide both HTML and plain text when writable clipboard data is available.
API Reference
For guides and examples, use the documentation site at samnn.dev. For constructor parameters, typedefs, and model details, use the generated Dart API reference.