Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions super_editor/.run/Demo_ Components in Components.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo: Components in Components" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/example/lib/main_components_in_components.dart" />
<method v="2" />
</configuration>
</component>
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ class HeaderWithHintComponentBuilder implements ComponentBuilder {
const HeaderWithHintComponentBuilder();

@override
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext context,
Document document,
DocumentNode node,
) {
// This component builder can work with the standard paragraph view model.
// We'll defer to the standard paragraph component builder to create it.
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ class UnselectableHrComponentBuilder implements ComponentBuilder {
const UnselectableHrComponentBuilder();

@override
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext context,
Document document,
DocumentNode node,
) {
// This builder can work with the standard horizontal rule view model, so
// we'll defer to the standard horizontal rule builder.
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ class AnimatedTaskComponentBuilder implements ComponentBuilder {
const AnimatedTaskComponentBuilder();

@override
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext context,
Document document,
DocumentNode node,
) {
// This builder can work with the standard task view model, so
// we'll defer to the standard task builder.
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,13 @@ class SpellingErrorParagraphComponentBuilder implements ComponentBuilder {
final UnderlineStyle underlineStyle;

@override
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
final viewModel = ParagraphComponentBuilder().createViewModel(document, node) as ParagraphComponentViewModel?;
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext context,
Document document,
DocumentNode node,
) {
final viewModel =
ParagraphComponentBuilder().createViewModel(context, document, node) as ParagraphComponentViewModel?;
if (viewModel == null) {
return null;
}
Expand Down
299 changes: 299 additions & 0 deletions super_editor/example/lib/main_components_in_components.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:super_editor/super_editor.dart';

void main() {
runApp(
MaterialApp(
home: _ComponentsInComponentsDemoScreen(),
),
);
}

class _ComponentsInComponentsDemoScreen extends StatefulWidget {
const _ComponentsInComponentsDemoScreen({super.key});

@override
State<_ComponentsInComponentsDemoScreen> createState() => _ComponentsInComponentsDemoScreenState();
}

class _ComponentsInComponentsDemoScreenState extends State<_ComponentsInComponentsDemoScreen> {
late final Editor _editor;

@override
void initState() {
super.initState();

_editor = createDefaultDocumentEditor(
document: MutableDocument(
nodes: [
ParagraphNode(
id: "1",
text: AttributedText("This is a demo of a Banner component."),
metadata: {
NodeMetadata.blockType: header1Attribution,
},
),
_BannerNode(id: "2", children: [
ParagraphNode(
id: "3",
text: AttributedText("Hello, Banner!"),
metadata: {
NodeMetadata.blockType: header1Attribution,
},
),
ParagraphNode(
id: "4",
text: AttributedText("This is a banner, which can contain any other blocks you want"),
),
]),
ParagraphNode(
id: "5",
text: AttributedText("This is after the banner component."),
),
],
),
composer: MutableDocumentComposer(),
);
}

@override
void dispose() {
_editor.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: SuperEditor(
editor: _editor,
componentBuilders: [
_BannerComponentBuilder(),
...defaultComponentBuilders,
],
),
);
}
}

class _BannerNode extends DocumentNode {
_BannerNode({
required this.id,
required this.children,
});

@override
final String id;

final List<DocumentNode> children;

@override
NodePosition get beginningPosition => CompositeNodePosition(
children.first.id,
children.first.beginningPosition,
);

@override
NodePosition get endPosition => CompositeNodePosition(
children.last.id,
children.last.endPosition,
);

@override
bool containsPosition(Object position) {
if (position is! CompositeNodePosition) {
return false;
}

for (final child in children) {
if (child.id == position.childNodeId) {
return child.containsPosition(position.childNodePosition);
}
}

return false;
}

@override
NodePosition selectUpstreamPosition(NodePosition position1, NodePosition position2) {
if (position1 is! CompositeNodePosition) {
throw Exception('Expected a _CompositeNodePosition for position1 but received a ${position1.runtimeType}');
}
if (position2 is! CompositeNodePosition) {
throw Exception('Expected a _CompositeNodePosition for position2 but received a ${position2.runtimeType}');
}

final index1 = int.parse(position1.childNodeId);
final index2 = int.parse(position2.childNodeId);

if (index1 == index2) {
return position1.childNodePosition ==
children[index1].selectUpstreamPosition(position1.childNodePosition, position2.childNodePosition)
? position1
: position2;
}

return index1 < index2 ? position1 : position2;
}

@override
NodePosition selectDownstreamPosition(NodePosition position1, NodePosition position2) {
final upstream = selectUpstreamPosition(position1, position2);
return upstream == position1 ? position2 : position1;
}

@override
NodeSelection computeSelection({required NodePosition base, required NodePosition extent}) {
assert(base is CompositeNodePosition);
assert(extent is CompositeNodePosition);

return BannerNodeSelection(
base: base as CompositeNodePosition,
extent: extent as CompositeNodePosition,
);
}

@override
DocumentNode copyWithAddedMetadata(Map<String, dynamic> newProperties) {
// TODO: implement copyWithAddedMetadata
throw UnimplementedError();
}

@override
DocumentNode copyAndReplaceMetadata(Map<String, dynamic> newMetadata) {
// TODO: implement copyAndReplaceMetadata
throw UnimplementedError();
}

@override
String? copyContent(NodeSelection selection) {
// TODO: implement copyContent
throw UnimplementedError();
}
}

class BannerNodeSelection implements NodeSelection {
const BannerNodeSelection.collapsed(CompositeNodePosition position)
: base = position,
extent = position;

const BannerNodeSelection({
required this.base,
required this.extent,
});

final CompositeNodePosition base;

final CompositeNodePosition extent;
}

class _BannerComponentBuilder implements ComponentBuilder {
@override
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext presenterContext,
Document document,
DocumentNode node,
) {
if (node is! _BannerNode) {
return null;
}

return _BannerViewModel(
nodeId: node.id,
children: node.children.map((childNode) => presenterContext.createViewModel(childNode)!).toList(),
);
}

@override
Widget? createComponent(
SingleColumnDocumentComponentContext componentContext,
SingleColumnLayoutComponentViewModel componentViewModel,
) {
if (componentViewModel is! _BannerViewModel) {
return null;
}

final childrenAndKeys = componentViewModel.children
.map(
(childViewModel) => componentContext.buildChildComponent(childViewModel),
)
.toList(growable: false);

print("Building a _BannerComponent - banner key: ${componentContext.componentKey}");
print(" - child keys: ${childrenAndKeys.map((x) => x.$1)}");
return _BannerComponent(
key: componentContext.componentKey,
// childComponentIds: [],
childComponentKeys: childrenAndKeys.map((childAndKey) => childAndKey.$1).toList(growable: false),
children: [
for (final child in childrenAndKeys) //
child.$2,
],
);
}
}

class _BannerViewModel extends SingleColumnLayoutComponentViewModel {
_BannerViewModel({
required super.nodeId,
super.createdAt,
super.padding = EdgeInsets.zero,
super.maxWidth,
required this.children,
});

final List<SingleColumnLayoutComponentViewModel> children;

@override
SingleColumnLayoutComponentViewModel copy() {
return _BannerViewModel(
nodeId: nodeId,
createdAt: createdAt,
padding: padding,
maxWidth: maxWidth,
children: List.from(children),
);
}
}

class _BannerComponent extends StatefulWidget {
const _BannerComponent({
super.key,
// required this.childComponentIds,
required this.childComponentKeys,
required this.children,
});

// final List<String> childComponentIds;

final List<GlobalKey<DocumentComponent>> childComponentKeys;

final List<Widget> children;

@override
State<_BannerComponent> createState() => _BannerComponentState();
}

class _BannerComponentState extends State<_BannerComponent> with ProxyDocumentComponent<_BannerComponent> {
@override
final childDocumentComponentKey = GlobalKey(debugLabel: 'banner-internal-column');

@override
Widget build(BuildContext context) {
return IgnorePointer(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: ColumnDocumentComponent(
key: childDocumentComponentKey,
// childComponentIds: widget.childComponentIds,
childComponentKeys: widget.childComponentKeys,
children: widget.children,
),
),
);
}
}
6 changes: 5 additions & 1 deletion super_editor/example_perf/lib/demos/rebuild_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ class AnimatedTaskComponentBuilder implements ComponentBuilder {
const AnimatedTaskComponentBuilder();

@override
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext context,
Document document,
DocumentNode node,
) {
// This builder can work with the standard task view model, so
// we'll defer to the standard task builder.
return null;
Expand Down
3 changes: 2 additions & 1 deletion super_editor/lib/src/default_editor/blockquote.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class BlockquoteComponentBuilder implements ComponentBuilder {
const BlockquoteComponentBuilder();

@override
SingleColumnLayoutComponentViewModel? createViewModel(Document document, DocumentNode node) {
SingleColumnLayoutComponentViewModel? createViewModel(
PresenterContext context, Document document, DocumentNode node) {
if (node is! ParagraphNode) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class DocumentImeInputClient extends TextInputConnectionDecorator with TextInput
editorImeLog.fine(
"Sending forceful update to IME because our local TextEditingValue didn't change, but the IME may have:");
editorImeLog.fine("$newValue");
print("Sending forceful update to IME because our local TextEditingValue didn't change, but the IME may have:");
print("$newValue");
imeConnection.value?.setEditingState(newValue);
} else {
editorImeLog.fine("Ignoring new TextEditingValue because it's the same as the existing one: $newValue");
Expand Down
Loading
Loading