rich_text_controller
Text editing controller highlighting words, prefixes and suffixes based on RegExp
s and String
s.
- User callback for all matched highlights
- User callback for tapped highlights
- User callback for selected highlights
- User callback for constructing complex
InlineSpan
s - Deletes highlights on backspace
- Exact word, prefix, postfix matching
- Disable highlighting and callbacks dynamically
Installation
flutter pub add rich_text_controllerx
Quick Start
MatchTargetItem
MatchTargetItem
defines words or paterns to match and the TextStyle
for matched words or patterns.
- The optional function
MatchTargetItem.onSelected
is invoked when a matched words is selected. - The optional function
MatchTargetItem.onTapInto
is invoked when a matched words is tapped into. The needed minimum difference in cursor position is two chars. - Combining
MatchTargetItem.onSelected
andMatchTargetItem.onTapInto
might not be useful.
MatchTargetItem.matchString
offers various String matching patterns:
enum StringMatch {
any, // No word boundaries, also inside a String
prefix, // The beginning of a String
postfix, // The beginning of a String
prefixComplete, // The beginning of a String and the complete word
postfixComplete, // The end of a String and the complete word
exact, // The exact String within its own word boundaries
}
MatchTargetItem.matchPayload
allows to set a custom payload, accessible in MatchResultItem.target.matchPayload
.
MatchTargetItem.inlineSpanBuilder
allows to define a custom function returning the InlineSpan to show. Useful for displaying tooltips etc.
RichTextController
The optional function RichTextController.onMatch
is invoked everytime the set of matched words change.
late final RichTextController controller = RichTextController(
text:
'Home sweet home! Do good homework and housework at home. The happyness of an ending.',
sortTargetsByPatternLength: true,
mergeOverlappingStyles: true,
deleteOnBack: true,
enableHighlight: true, // toggable at runtime
enableOnTapInto: true, // toggable at runtime
enableOnSelected: true, // toggable at runtime
onMatch: (List<MatchResultItem> matches) {
for (final MatchResultItem match in matches) {
log(match.toString());
}
},
targets: [
// --------------------------------------------------------------
// SINGLE STRING
// --------------------------------------------------------------
MatchTargetItem.string(
'homework',
style:
const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold),
stringMatch: StringMatch.exact,
caseSensitive: false,
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
MatchTargetItem.string(
'Home',
style: const TextStyle(color: Colors.red),
stringMatch: StringMatch.any,
caseSensitive: false,
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
// Prefix
MatchTargetItem.string(
'happy',
style: const TextStyle(color: Colors.blue),
stringMatch: StringMatch.prefix,
caseSensitive: false,
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
// Postfix
MatchTargetItem.string(
'ing',
style: const TextStyle(color: Colors.blue),
stringMatch: StringMatch.postfix,
caseSensitive: false,
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
// --------------------------------------------------------------
// MULTIPLE STRINGS
// --------------------------------------------------------------
MatchTargetItem.strings(
['do', 'at'],
style: const TextStyle(color: Colors.pink),
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
// --------------------------------------------------------------
// SINGLE REGEXP
// --------------------------------------------------------------
MatchTargetItem.regex(
RegExp(r'sweet'),
style: const TextStyle(color: Colors.indigo),
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
// --------------------------------------------------------------
// MUTIPLE REGEXPs
// --------------------------------------------------------------
MatchTargetItem.regexes(
[RegExp(r'good'), RegExp(r'and')],
style: const TextStyle(
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.wavy,
decorationColor: Colors.indigo,
decorationThickness: 3,
),
onSelected: (MatchResultItem match) => _onWordMatchSelected(match),
onTapInto: (MatchResultItem match) => _onWordMatchTap(match),
),
],
);
// ...
TextField(controller: controller)
RichWrapper
RichWrapper(
initialText: 'Good morning and good night at Github',
targets: [
MatchTargetItem.string('good',
style: const TextStyle(color: Colors.red)),
MatchTargetItem.strings(['morning', 'night'],
style: const TextStyle(color: Colors.blue)),
MatchTargetItem.regex(RegExp(r'and'),
style: const TextStyle(color: Colors.orange)),
MatchTargetItem.regexs([RegExp(r'at'), RegExp(r'Github')],
style: const TextStyle(color: Colors.indigo)),
],
child: (controller) => TextField(controller: controller),
)
InlineSpan-Builder
The inlineSpanBuilder
is an optional custom builder. In the example below,
it returns a WidgetSpan
showing aTooltip
.
If the inlineSpanBuilder
is null
, highlighting is done as per style
definition in MatchTargetItem
.
MatchTargetItem.strings(
['thing', 'it', 'there was', 'used to'],
style: TextStyle(color: Colors.red),
stringMatch: StringMatch.exact,
///
/// A custom payload, may be `null`.
///
matchPayload: WritingAidItem(
'Vague words: Underspecified',
'Not providing concrete information',
),
///
/// InlineSpanBui8uilder returns a `WidgetSpan` with `Tooltip`.
/// The `Tooltip` is based on the object defined in [matchPayload].
///
inlineSpanBuilder: (
String match,
TextStyle matchStyle,
WritingAidItem? matchPayload) {
InlineSpan ret = TextSpan(text: match, style: matchStyle);
if (matchPayload != null) {
final TextSpan toolTipRichMessage = TextSpan(
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(color: Colors.white),
children: [
TextSpan(
text: match,
style: const TextStyle(fontStyle: FontStyle.italic)),
const TextSpan(text: '\n'),
TextSpan(
text: matchPayload.category,
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: '\n'),
TextSpan(
text: matchPayload.description,
style: const TextStyle(fontStyle: FontStyle.normal)),
]);
ret = WidgetSpan(
child: Tooltip(
richMessage: toolTipRichMessage,
child: Text(match, style: matchStyle)));
}
return ret;
}
),
Example
See the example app in example/flutter_example
for code.
Inspiration
This package is a complete rewrite of https://github.com/micazi/rich_text_controller.
License
This project is licensed under the MIT License - see the LICENSE.md file for details