typeset 3.0.0-beta.3
typeset: ^3.0.0-beta.3 copied to clipboard
Flutter inline text formatter for chat-style markup with plug-and-play usage and granular configuration for rendering, links, and editing.
✨ TypeSet #
Chat-style inline text formatting for Flutter
Getting Started · Usage · Configuration · Editing · Migration
📖 Overview #
TypeSet brings WhatsApp / Telegram-style inline formatting to Flutter with two design goals:
| Goal | What it means |
|---|---|
| Plug-and-play | Drop in one widget — zero config needed |
| Granular control | Tune styles, AutoLink policy, and editing behavior when you need it |
Why TypeSet? #
- 🔤 Familiar
*bold*/_italic_syntax users already know - 🌐 Backend-driven — style text from your server without app updates
- 🔗 Configurable AutoLink with scheme & domain allowlists
- ✏️ Real-time editing preview via
TypeSetEditingController - 🎨 Theme-aware styles with
TypeSetStyle.fromTheme() - ⚡ AST-based parser with LRU document caching
- 🧪 95%+ test coverage
📱 TypeSet in action! #
🚀 Getting Started #
Installation #
dependencies:
typeset: ^3.0.0
flutter pub get
Minimum Requirements #
| Requirement | Version |
|---|---|
| Dart SDK | >=3.0.0 <4.0.0 |
| Flutter | >=3.10.0 |
Coming from v2.x? See the Migration Guide for step-by-step upgrade instructions.
💡 Usage #
Quick Start #
import 'package:typeset/typeset.dart';
// That's it — one widget, zero config
const TypeSet('Hello *world* from _TypeSet_.');
Formatting Syntax #
| Style | Syntax | Output |
|---|---|---|
| Bold | *text* |
text |
| Italic | _text_ |
text |
| Underline | __text__ |
text |
~text~ |
||
Monospace |
`text` |
text |
| Escape | \*literal\* |
*literal* |
AutoLink (URL Detection) #
Raw URLs like https://flutter.dev and www.example.com are automatically detected and rendered as links when AutoLink is enabled (default).
Note
- Tap handling is opt-in via
TypeSetAutoLinkConfig.linkRecognizerBuilder. - Without a recognizer, links are styled but not interactive.
- AutoLink respects
allowedSchemesandallowedDomainsfor safety.
String Extension #
// Use the .typeset() extension for inline usage
'Hello *world*'.typeset(style: myTextStyle);
// Extract plain text (markers removed)
final plain = 'Hello *world*'.plainText; // "Hello world"
⚙️ Configuration #
TypeSet uses a layered config system with clear precedence:
Local config → Scoped provider → Global config → Library defaults
(highest priority) (lowest priority)
TypeSetConfig #
Centralizes all style and AutoLink behavior:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:typeset/typeset.dart';
final config = TypeSetConfig(
style: const TypeSetStyle(
boldStyle: TextStyle(fontWeight: FontWeight.w900),
italicStyle: TextStyle(fontStyle: FontStyle.italic, color: Color(0xFF6A1B9A)),
underlineStyle: TextStyle(decorationThickness: 3),
monospaceStyle: TextStyle(fontFamily: 'Courier', backgroundColor: Color(0xFFEFF3FF)),
linkStyle: TextStyle(color: Color(0xFF0B5FFF), decoration: TextDecoration.underline),
markerColor: Color(0xFF8D8D8D),
),
autoLinkConfig: TypeSetAutoLinkConfig(
allowedSchemes: {'https'},
allowedDomains: RegExp(r'^flutter\.dev$'),
linkRecognizerBuilder: (text, url) =>
TapGestureRecognizer()..onTap = () => launchUrl(Uri.parse(url)),
),
);
TypeSet('Read *docs* at https://flutter.dev', config: config);
Theme-Aware Styles #
// Automatically adapts to your app's ColorScheme
final style = TypeSetStyle.fromTheme(Theme.of(context));
📋 Full TypeSetStyle properties
| Property | Type | Description |
|---|---|---|
boldStyle |
TextStyle? |
Style for *bold* text |
italicStyle |
TextStyle? |
Style for _italic_ text |
underlineStyle |
TextStyle? |
Style for __underline__ text |
strikethroughStyle |
TextStyle? |
Style for ~strikethrough~ text |
linkStyle |
TextStyle? |
Style for AutoLinked URLs |
monospaceStyle |
TextStyle? |
Style for `code` text |
markerColor |
Color? |
Color for formatting markers (editing mode) |
🔗 Full TypeSetAutoLinkConfig properties
| Property | Type | Description |
|---|---|---|
allowedSchemes |
Set<String>? |
Allowed URL schemes (default: {'http', 'https'}) |
allowedDomains |
RegExp? |
Regex filter for allowed hostnames |
customValidator |
bool Function(Uri)? |
Additional URI validation callback |
linkRecognizerBuilder |
GestureRecognizer Function(String, String)? |
Builds a recognizer for each link |
Presets:
| Preset | Description |
|---|---|
TypeSetAutoLinkConfig.httpsOnly |
Only https links |
TypeSetAutoLinkConfig.disabled |
No AutoLink detection |
Scoped Configuration #
Apply config to a subtree using TypeSetConfigProvider:
TypeSetConfigProvider(
config: TypeSetConfig.defaults().copyWith(
autoLinkConfig: TypeSetAutoLinkConfig.httpsOnly,
),
child: const TypeSet('Visit https://dart.dev'),
);
Global Configuration #
Set app-wide defaults at startup:
void main() {
TypeSetGlobalConfig.current = TypeSetConfig(
autoLinkConfig: TypeSetAutoLinkConfig.httpsOnly,
);
runApp(const MyApp());
}
Tip
Per-widget config: always takes the highest priority, so you can override any default locally.
✏️ Editing #
TypeSetEditingController #
Drop-in replacement for TextEditingController with live formatting preview:
final controller = TypeSetEditingController(
text: 'Use *bold* and __underline__',
config: TypeSetConfig(
style: const TypeSetStyle(markerColor: Color(0xFF9E9E9E)),
),
);
TextField(
controller: controller,
maxLines: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Type formatted text…',
),
);
Note
Live formatting automatically disables when text exceeds maxLiveFormattingLength (default: 5000 chars) to prevent UI jank.
Context Menu Integration #
Add formatting buttons to the text selection toolbar:
TextField(
controller: controller,
contextMenuBuilder: (context, editableTextState) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: [
...getTypesetContextMenus(
editableTextState: editableTextState,
actions: [
TypesetFormatAction.bold,
TypesetFormatAction.italic,
TypesetFormatAction.underline,
],
),
...editableTextState.contextMenuButtonItems,
],
);
},
);
Available TypesetFormatAction values
| Action | Wraps with |
|---|---|
bold |
*text* |
italic |
_text_ |
strikethrough |
~text~ |
monospace |
`text` |
underline |
__text__ |
🏗️ Architecture #
Parser pipeline (under the hood)
TypeSet uses a deterministic 3-stage pipeline:
Input string → Parser (AST) → AutoLink pass → Renderer (InlineSpans)
- Parse — Single left-to-right pass with a frame stack → typed AST nodes
- AutoLink — Transforms URL-like text nodes into link nodes (config-driven)
- Render — Maps AST nodes to Flutter
InlineSpans
Performance: ~O(n) for all stages. An LRU cache (TypeSetDocumentCache) avoids re-parsing identical inputs.
For full details, see doc/UNDER_THE_HOOD.md.
🔄 Migration from v2.x #
Version 3.0.0 includes breaking changes. The upgrade is straightforward — see the full guide:
👉 MIGRATION.md — step-by-step instructions with before/after code
Quick summary of what changed:
| Area | v2.x | v3.0 |
|---|---|---|
| Underline syntax | #text# |
__text__ |
| Escape character | ¦ (broken bar) |
\ (backslash) |
| Widget styling | Individual params (linkStyle, boldStyle, …) |
TypeSetConfig object |
| Controller styling | Individual params (markerColor, linkStyle, …) |
TypeSetConfig object |
| Link syntax | §text|url§ (explicit marker) |
AutoLink detection (URLs auto-detected) |
| Context menu link | StyleTypeEnum.link |
Removed (use AutoLink instead) |
| Font size syntax | text<24> |
Removed |
| Dependency | url_launcher required |
No external dependencies |
| Config scope | Per-widget only | Global → Scoped → Local cascade |
📂 Example App #
cd example
flutter run
🤝 Contributing #
Contributions welcome! See CONTRIBUTING.md for guidelines.
📝 License #
Apache-2.0 — see LICENSE.
