Skip to content

Commit f4c5bbe

Browse files
[SuperEditor] - Fix: Use inline widget builders in hint component (Resolves #2783) (#2784)
1 parent 652361e commit f4c5bbe

File tree

4 files changed

+86
-55
lines changed

4 files changed

+86
-55
lines changed

super_editor/lib/src/default_editor/paragraph.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ class HintComponentBuilder extends ParagraphComponentBuilder {
331331
return TextWithHintComponent(
332332
key: componentContext.componentKey,
333333
text: componentViewModel.text,
334+
inlineWidgetBuilders: componentViewModel.inlineWidgetBuilders,
334335
textStyleBuilder: componentViewModel.textStyleBuilder,
335336
hintText: AttributedText(componentViewModel.hintText),
336337
hintStyleBuilder: (attributions) => hintStyleBuilder(componentContext.context),
@@ -422,6 +423,7 @@ class HintComponentViewModel extends SingleColumnLayoutComponentViewModel with T
422423
createdAt: createdAt,
423424
padding: padding,
424425
text: text.copy(),
426+
inlineWidgetBuilders: inlineWidgetBuilders,
425427
textStyleBuilder: textStyleBuilder,
426428
opacity: opacity,
427429
selectionColor: selectionColor,

super_editor/lib/src/default_editor/text.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ class TextWithHintComponent extends StatefulWidget {
691691
const TextWithHintComponent({
692692
Key? key,
693693
required this.text,
694+
this.inlineWidgetBuilders = const [],
694695
this.hintText,
695696
this.hintStyleBuilder,
696697
this.textAlign,
@@ -705,6 +706,10 @@ class TextWithHintComponent extends StatefulWidget {
705706
}) : super(key: key);
706707

707708
final AttributedText text;
709+
710+
/// {@macro text_component_inline_widget_builders}
711+
final InlineWidgetBuilderChain inlineWidgetBuilders;
712+
708713
final AttributedText? hintText;
709714
final AttributionStyleBuilder? hintStyleBuilder;
710715
final TextAlign? textAlign;
@@ -757,6 +762,7 @@ class _TextWithHintComponentState extends State<TextWithHintComponent>
757762
TextComponent(
758763
key: _childTextComponentKey,
759764
text: widget.text,
765+
inlineWidgetBuilders: widget.inlineWidgetBuilders,
760766
textAlign: widget.textAlign,
761767
textDirection: widget.textDirection,
762768
textStyleBuilder: widget.textStyleBuilder,
@@ -805,10 +811,12 @@ class TextComponent extends StatefulWidget {
805811

806812
final AttributionStyleBuilder textStyleBuilder;
807813

814+
/// {@template text_component_inline_widget_builders}
808815
/// A Chain of Responsibility that's used to build inline widgets.
809816
///
810817
/// The first builder in the chain to return a non-null `Widget` will be
811818
/// used for a given inline placeholder.
819+
/// {@endtemplate}
812820
final InlineWidgetBuilderChain inlineWidgetBuilders;
813821

814822
final Map<String, dynamic> metadata;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'package:flutter/material.dart' show Colors;
2+
import 'package:flutter/widgets.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:flutter_test_runners/flutter_test_runners.dart';
5+
import 'package:super_editor/src/test/super_editor_test/supereditor_inspector.dart';
6+
import 'package:super_editor/super_editor.dart';
7+
8+
import '../supereditor_test_tools.dart';
9+
10+
void main() {
11+
group("Super Editor > components > hint text >", () {
12+
testWidgetsOnArbitraryDesktop("displays inline widgets", (tester) async {
13+
await tester
14+
.createDocument()
15+
.withCustomContent(MutableDocument(
16+
nodes: [
17+
ParagraphNode(
18+
id: "1",
19+
text: AttributedText("Hello to ", null, {
20+
9: "fake_mention",
21+
}))
22+
],
23+
))
24+
.withComponentBuilders([
25+
const HintComponentBuilder("Hello", _hintStyler),
26+
...defaultComponentBuilders,
27+
])
28+
.useStylesheet(defaultStylesheet.copyWith(
29+
inlineWidgetBuilders: _inlineWidgetBuilders,
30+
))
31+
.pump();
32+
33+
// Ensure that we really are using the hint text component.
34+
expect(find.byType(TextWithHintComponent), findsOne);
35+
36+
final richText = SuperEditorInspector.findRichTextInParagraph("1");
37+
expect(richText.children, isNotNull);
38+
39+
// Verify that we show the text in the node.
40+
expect(richText.children!.first, isA<TextSpan>());
41+
expect((richText.children!.first as TextSpan).text, "Hello to ");
42+
43+
// Verify that we built the inline widget for the place holder in the node.
44+
expect(richText.children!.last, isA<WidgetSpan>());
45+
expect((richText.children!.last as WidgetSpan).child, isA<_FakeInlineWidget>());
46+
});
47+
});
48+
}
49+
50+
TextStyle _hintStyler(BuildContext _) => TextStyle();
51+
52+
const _inlineWidgetBuilders = [
53+
_buildFakeInlineWidget,
54+
];
55+
56+
Widget? _buildFakeInlineWidget(BuildContext context, TextStyle style, Object placeholder) {
57+
if (placeholder is! String || placeholder != "fake_mention") {
58+
return null;
59+
}
60+
61+
return const _FakeInlineWidget();
62+
}
63+
64+
class _FakeInlineWidget extends StatelessWidget {
65+
const _FakeInlineWidget();
66+
67+
@override
68+
Widget build(BuildContext context) {
69+
return Container(
70+
width: 16,
71+
height: 16,
72+
color: Colors.red,
73+
);
74+
}
75+
}

super_editor/test/super_editor/supereditor_components_test.dart

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ void main() {
1616
await tester //
1717
.createDocument()
1818
.withSingleEmptyParagraph()
19-
.withAddedComponents([const HintTextComponentBuilder()])
19+
.withAddedComponents([HintComponentBuilder("Hello", (_) => const TextStyle())])
2020
.autoFocus(false)
2121
.pump();
2222

@@ -96,55 +96,6 @@ void main() {
9696
});
9797
}
9898

99-
class HintTextComponentBuilder implements ComponentBuilder {
100-
const HintTextComponentBuilder();
101-
102-
@override
103-
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
104-
// This component builder can work with the standard paragraph view model.
105-
// We'll defer to the standard paragraph component builder to create it.
106-
return null;
107-
}
108-
109-
@override
110-
Widget? createComponent(
111-
SingleColumnDocumentComponentContext componentContext, SingleColumnLayoutComponentViewModel componentViewModel) {
112-
if (componentViewModel is! ParagraphComponentViewModel) {
113-
return null;
114-
}
115-
116-
final textSelection = componentViewModel.selection;
117-
118-
return TextWithHintComponent(
119-
key: componentContext.componentKey,
120-
text: componentViewModel.text,
121-
textStyleBuilder: defaultStyleBuilder,
122-
metadata: componentViewModel.blockType != null
123-
? {
124-
'blockType': componentViewModel.blockType,
125-
}
126-
: {},
127-
// This is the text displayed as a hint.
128-
hintText: AttributedText(
129-
'this is hint text...',
130-
AttributedSpans(
131-
attributions: [
132-
const SpanMarker(attribution: italicsAttribution, offset: 12, markerType: SpanMarkerType.start),
133-
const SpanMarker(attribution: italicsAttribution, offset: 15, markerType: SpanMarkerType.end),
134-
],
135-
),
136-
),
137-
// This is the function that selects styles for the hint text.
138-
hintStyleBuilder: (Set<Attribution> attributions) => defaultStyleBuilder(attributions).copyWith(
139-
color: const Color(0xFFDDDDDD),
140-
),
141-
textSelection: textSelection,
142-
selectionColor: componentViewModel.selectionColor,
143-
underlines: componentViewModel.createUnderlines(),
144-
);
145-
}
146-
}
147-
14899
/// Pump a SuperEditor containing an image which will render as an 100x100 box
149100
/// and content big enough to cause the document to be scrollable.
150101
Future<void> _pumpImageTestApp(
@@ -223,9 +174,4 @@ class _UnknownNode extends BlockNode {
223174
_UnknownNode copyAndReplaceMetadata(Map<String, dynamic> newMetadata) {
224175
return _UnknownNode(id: id);
225176
}
226-
227-
@override
228-
_UnknownNode copy() {
229-
return _UnknownNode(id: id);
230-
}
231177
}

0 commit comments

Comments
 (0)