Skip to content

Commit c5f2b66

Browse files
[SuperEditor] Expand usability of HintComponentBuilder. (Resolves #2655)
1 parent 42e3aff commit c5f2b66

6 files changed

Lines changed: 341 additions & 126 deletions

File tree

super_clones/slack/lib/mobile_message_editor.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ class _MobileMessageEditorState extends State<MobileMessageEditor> {
265265
Expanded(
266266
child: Text(
267267
widget.hintText,
268-
style: _hintTextStyleBuilder(context),
268+
style: _hintTextStyleBuilder(context, {}),
269269
),
270270
),
271271
IconButton(
@@ -364,9 +364,9 @@ class _ChatEditorState extends State<_ChatEditor> {
364364
shrinkWrap: false,
365365
stylesheet: messageEditorStylesheet,
366366
componentBuilders: [
367-
HintComponentBuilder(
367+
HintComponentBuilder.basic(
368368
widget.hintText,
369-
_hintTextStyleBuilder,
369+
hintStyleBuilder: _hintTextStyleBuilder,
370370
),
371371
...defaultComponentBuilders,
372372
],
@@ -375,7 +375,7 @@ class _ChatEditorState extends State<_ChatEditor> {
375375
}
376376
}
377377

378-
TextStyle _hintTextStyleBuilder(context) => TextStyle(
378+
TextStyle _hintTextStyleBuilder(BuildContext context, Set<Attribution> attributions) => TextStyle(
379379
fontSize: 14,
380380
color: Colors.grey,
381381
);

super_editor/example/lib/demos/components/demo_text_with_hint.dart

Lines changed: 126 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class _TextWithHintDemoState extends State<TextWithHintDemo> {
2222
late MutableDocument _doc;
2323
late MutableDocumentComposer _composer;
2424
late Editor _docEditor;
25+
_HintDemoMode _demoMode = _HintDemoMode.header1;
2526

2627
@override
2728
void initState() {
@@ -37,21 +38,62 @@ class _TextWithHintDemoState extends State<TextWithHintDemo> {
3738
super.dispose();
3839
}
3940

40-
/// Creates a document with multiple levels of headers with hint text, and a
41-
/// regular paragraph for comparison.
41+
void _resetDocument() {
42+
setState(() {
43+
_doc = _createDocument();
44+
_composer = MutableDocumentComposer();
45+
_docEditor = createDefaultDocumentEditor(document: _doc, composer: _composer);
46+
});
47+
}
48+
4249
MutableDocument _createDocument() {
50+
return switch (_demoMode) {
51+
_HintDemoMode.header1 => _createH1Document(),
52+
_HintDemoMode.header2 => _createH2Document(),
53+
_HintDemoMode.header3 => _createH3Document(),
54+
_HintDemoMode.paragraph => _createParagraphDocument(),
55+
};
56+
}
57+
58+
MutableDocument _createH1Document() {
4359
return MutableDocument(
4460
nodes: [
4561
ParagraphNode(
4662
id: Editor.createNodeId(),
4763
text: AttributedText(),
4864
metadata: {'blockType': header1Attribution},
4965
),
66+
ParagraphNode(
67+
id: Editor.createNodeId(),
68+
text: AttributedText(
69+
'Nam hendrerit vitae elit ut placerat. Maecenas nec congue neque. Fusce eget tortor pulvinar, cursus neque vitae, sagittis lectus. Duis mollis libero eu scelerisque ullamcorper. Pellentesque eleifend arcu nec augue molestie, at iaculis dui rutrum. Etiam lobortis magna at magna pellentesque ornare. Sed accumsan, libero vel porta molestie, tortor lorem eleifend ante, at egestas leo felis sed nunc. Quisque mi neque, molestie vel dolor a, eleifend tempor odio.',
70+
),
71+
),
72+
],
73+
);
74+
}
75+
76+
MutableDocument _createH2Document() {
77+
return MutableDocument(
78+
nodes: [
5079
ParagraphNode(
5180
id: Editor.createNodeId(),
5281
text: AttributedText(),
5382
metadata: {'blockType': header2Attribution},
5483
),
84+
ParagraphNode(
85+
id: Editor.createNodeId(),
86+
text: AttributedText(
87+
'Nam hendrerit vitae elit ut placerat. Maecenas nec congue neque. Fusce eget tortor pulvinar, cursus neque vitae, sagittis lectus. Duis mollis libero eu scelerisque ullamcorper. Pellentesque eleifend arcu nec augue molestie, at iaculis dui rutrum. Etiam lobortis magna at magna pellentesque ornare. Sed accumsan, libero vel porta molestie, tortor lorem eleifend ante, at egestas leo felis sed nunc. Quisque mi neque, molestie vel dolor a, eleifend tempor odio.',
88+
),
89+
),
90+
],
91+
);
92+
}
93+
94+
MutableDocument _createH3Document() {
95+
return MutableDocument(
96+
nodes: [
5597
ParagraphNode(
5698
id: Editor.createNodeId(),
5799
text: AttributedText(),
@@ -67,25 +109,86 @@ class _TextWithHintDemoState extends State<TextWithHintDemo> {
67109
);
68110
}
69111

112+
MutableDocument _createParagraphDocument() {
113+
return MutableDocument(
114+
nodes: [
115+
ParagraphNode(
116+
id: Editor.createNodeId(),
117+
text: AttributedText(),
118+
),
119+
ParagraphNode(
120+
id: Editor.createNodeId(),
121+
text: AttributedText(
122+
'Nam hendrerit vitae elit ut placerat. Maecenas nec congue neque. Fusce eget tortor pulvinar, cursus neque vitae, sagittis lectus. Duis mollis libero eu scelerisque ullamcorper. Pellentesque eleifend arcu nec augue molestie, at iaculis dui rutrum. Etiam lobortis magna at magna pellentesque ornare. Sed accumsan, libero vel porta molestie, tortor lorem eleifend ante, at egestas leo felis sed nunc. Quisque mi neque, molestie vel dolor a, eleifend tempor odio.',
123+
),
124+
),
125+
],
126+
);
127+
}
128+
70129
@override
71130
Widget build(BuildContext context) {
72-
return SuperEditor(
73-
editor: _docEditor,
74-
stylesheet: Stylesheet(
75-
documentPadding: const EdgeInsets.symmetric(vertical: 56, horizontal: 24),
76-
rules: defaultStylesheet.rules,
77-
78-
/// Adjust the default styles to style 3 levels of headers
79-
/// with large font sizes.
80-
inlineTextStyler: (attributions, style) => style.merge(_textStyleBuilder(attributions)),
81-
),
131+
return Scaffold(
132+
body: SuperEditor(
133+
editor: _docEditor,
134+
stylesheet: Stylesheet(
135+
documentPadding: const EdgeInsets.symmetric(vertical: 56, horizontal: 24),
136+
rules: defaultStylesheet.rules,
137+
138+
/// Adjust the default styles to style 3 levels of headers
139+
/// with large font sizes.
140+
inlineTextStyler: (attributions, style) => style.merge(_textStyleBuilder(attributions)),
141+
),
82142

83-
/// Add a new component builder to the front of the list
84-
/// that knows how to render header widgets with hint text.
85-
componentBuilders: [
86-
const HeaderWithHintComponentBuilder(),
87-
...defaultComponentBuilders,
88-
],
143+
/// Add a new component builder to the front of the list
144+
/// that knows how to render header widgets with hint text.
145+
componentBuilders: [
146+
HintComponentBuilder.attributed(
147+
AttributedText(
148+
'Header goes here...',
149+
AttributedSpans(
150+
attributions: [
151+
const SpanMarker(attribution: italicsAttribution, offset: 12, markerType: SpanMarkerType.start),
152+
const SpanMarker(attribution: italicsAttribution, offset: 15, markerType: SpanMarkerType.end),
153+
],
154+
),
155+
),
156+
hintStyleBuilder: (context, attributions) => _textStyleBuilder(attributions).copyWith(
157+
color: const Color(0xFFDDDDDD),
158+
),
159+
shouldShowHint: (document, node) => document.getNodeIndexById(node.id) == 0 && node.text.isEmpty,
160+
),
161+
...defaultComponentBuilders,
162+
],
163+
),
164+
bottomNavigationBar: BottomNavigationBar(
165+
type: BottomNavigationBarType.fixed,
166+
currentIndex: _demoMode.index,
167+
items: const [
168+
BottomNavigationBarItem(
169+
icon: Icon(Icons.title),
170+
label: 'Header 1',
171+
),
172+
BottomNavigationBarItem(
173+
icon: Icon(Icons.title),
174+
label: 'Hearder 2',
175+
),
176+
BottomNavigationBarItem(
177+
icon: Icon(Icons.title),
178+
label: 'Header 3',
179+
),
180+
BottomNavigationBarItem(
181+
icon: Icon(Icons.short_text),
182+
label: 'Paragraph',
183+
),
184+
],
185+
onTap: (int newIndex) {
186+
setState(() {
187+
_demoMode = _HintDemoMode.values[newIndex];
188+
_resetDocument();
189+
});
190+
},
191+
),
89192
);
90193
}
91194
}
@@ -122,76 +225,9 @@ TextStyle _textStyleBuilder(Set<Attribution> attributions) {
122225
return newStyle;
123226
}
124227

125-
/// SuperEditor [ComponentBuilder] that builds a component for Header 1, Header 2,
126-
/// and Header 3 `ParagraphNode`s, displays "header goes here..." when the content
127-
/// text is empty.
128-
///
129-
/// [ComponentBuilder]s operate at the document level, which means that they can
130-
/// make decisions based on global document structure. Therefore, if you'd like
131-
/// to limit hint text to the very first header in a document, or the first header
132-
/// and paragraph, you can make that decision at the beginning of your
133-
/// [ComponentBuilder]:
134-
///
135-
/// ```
136-
/// final nodeIndex = componentContext.document.getNodeIndex(
137-
/// componentContext.documentNode,
138-
/// );
139-
///
140-
/// if (nodeIndex > 0) {
141-
/// // This isn't the first node, we don't ever want to show hint text.
142-
/// return null;
143-
/// }
144-
/// ```
145-
class HeaderWithHintComponentBuilder implements ComponentBuilder {
146-
const HeaderWithHintComponentBuilder();
147-
148-
@override
149-
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
150-
// This component builder can work with the standard paragraph view model.
151-
// We'll defer to the standard paragraph component builder to create it.
152-
return null;
153-
}
154-
155-
@override
156-
Widget? createComponent(
157-
SingleColumnDocumentComponentContext componentContext, SingleColumnLayoutComponentViewModel componentViewModel) {
158-
if (componentViewModel is! ParagraphComponentViewModel) {
159-
return null;
160-
}
161-
162-
final blockAttribution = componentViewModel.blockType;
163-
if (!(const [header1Attribution, header2Attribution, header3Attribution]).contains(blockAttribution)) {
164-
return null;
165-
}
166-
167-
final textSelection = componentViewModel.selection;
168-
169-
return TextWithHintComponent(
170-
key: componentContext.componentKey,
171-
text: componentViewModel.text,
172-
textStyleBuilder: _textStyleBuilder,
173-
metadata: componentViewModel.blockType != null
174-
? {
175-
'blockType': componentViewModel.blockType,
176-
}
177-
: {},
178-
// This is the text displayed as a hint.
179-
hintText: AttributedText(
180-
'header goes here...',
181-
AttributedSpans(
182-
attributions: [
183-
const SpanMarker(attribution: italicsAttribution, offset: 12, markerType: SpanMarkerType.start),
184-
const SpanMarker(attribution: italicsAttribution, offset: 15, markerType: SpanMarkerType.end),
185-
],
186-
),
187-
),
188-
// This is the function that selects styles for the hint text.
189-
hintStyleBuilder: (Set<Attribution> attributions) => _textStyleBuilder(attributions).copyWith(
190-
color: const Color(0xFFDDDDDD),
191-
),
192-
textSelection: textSelection,
193-
selectionColor: componentViewModel.selectionColor,
194-
underlines: componentViewModel.createUnderlines(),
195-
);
196-
}
228+
enum _HintDemoMode {
229+
header1,
230+
header2,
231+
header3,
232+
paragraph,
197233
}

super_editor/example_chat/lib/message_page_scaffold_demo/demo_super_editor_message_page.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,10 @@ class _ChatEditorState extends State<_ChatEditor> {
546546
shrinkWrap: false,
547547
stylesheet: _chatStylesheet,
548548
componentBuilders: [
549-
const HintComponentBuilder("Send a message...", _hintTextStyleBuilder),
549+
HintComponentBuilder.basic(
550+
"Send a message...",
551+
hintStyleBuilder: _hintTextStyleBuilder,
552+
),
550553
...defaultComponentBuilders,
551554
],
552555
),
@@ -640,7 +643,7 @@ final _chatStylesheet = Stylesheet(
640643
inlineWidgetBuilders: defaultInlineWidgetBuilderChain,
641644
);
642645

643-
TextStyle _hintTextStyleBuilder(context) => TextStyle(
646+
TextStyle _hintTextStyleBuilder(BuildContext context, Set<Attribution> attributions) => TextStyle(
644647
color: Colors.grey,
645648
);
646649

0 commit comments

Comments
 (0)