feat: allow passing in a context menu builder (#1152)

* feat: allow passing in a context menu builder

* test: fix test

* chore: dart analyzer
This commit is contained in:
Richard Shiue 2025-11-06 11:48:13 +08:00 • committed by GitHub
commit 387a90ce39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 39 additions and 24 deletions

View file

@ -26,7 +26,7 @@ class AppFlowyEditor extends StatefulWidget {
Map<String, BlockComponentBuilder>? blockComponentBuilders,
List<CharacterShortcutEvent>? characterShortcutEvents,
List<CommandShortcutEvent>? commandShortcutEvents,
List<List<ContextMenuItem>>? contextMenuItems,
this.contextMenuBuilder,
this.contentInsertionConfiguration,
this.editable = true,
this.autoFocus = false,
@ -53,8 +53,7 @@ class AppFlowyEditor extends StatefulWidget {
characterShortcutEvents =
characterShortcutEvents ?? standardCharacterShortcutEvents,
commandShortcutEvents =
commandShortcutEvents ?? standardCommandShortcutEvents,
contextMenuItems = contextMenuItems ?? standardContextMenuItems;
commandShortcutEvents ?? standardCommandShortcutEvents;
final EditorState editorState;
@ -122,16 +121,14 @@ class AppFlowyEditor extends StatefulWidget {
/// ```
final List<CommandShortcutEvent> commandShortcutEvents;
/// The context menu items.
/// The context menu builder.
///
/// They will be shown when the user right click on the editor.
/// Each item will be separated by a divider.
/// It will be shown when the user right click on the editor.
///
/// Defaults to [standardContextMenuItems].
/// Defaults to a standard implementation containing
/// [standardContextMenuItems].
///
/// If empty the context menu won't appear.
///
final List<List<ContextMenuItem>>? contextMenuItems;
final ContextMenuWidgetBuilder? contextMenuBuilder;
/// Provide a editorScrollController to control the scroll behavior
///
@ -345,7 +342,7 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
cursorColor: widget.editorStyle.cursorColor,
selectionColor: widget.editorStyle.selectionColor,
showMagnifier: widget.showMagnifier,
contextMenuItems: widget.contextMenuItems,
contextMenuBuilder: widget.contextMenuBuilder,
dropTargetStyle: widget.dropTargetStyle,
child: child,
);

View file

@ -11,7 +11,7 @@ class DesktopSelectionServiceWidget extends StatefulWidget {
super.key,
this.cursorColor = const Color(0xFF00BCF0),
this.selectionColor = const Color(0xFF00BCF0),
this.contextMenuItems,
this.contextMenuBuilder,
required this.child,
this.dropTargetStyle = const AppFlowyDropTargetStyle(),
});
@ -19,7 +19,7 @@ class DesktopSelectionServiceWidget extends StatefulWidget {
final Widget child;
final Color cursorColor;
final Color selectionColor;
final List<List<ContextMenuItem>>? contextMenuItems;
final ContextMenuWidgetBuilder? contextMenuBuilder;
final AppFlowyDropTargetStyle dropTargetStyle;
@override
@ -425,8 +425,8 @@ class _DesktopSelectionServiceWidgetState
void _showContextMenu(TapDownDetails details) {
_clearContextMenu();
// Don't trigger the context menu if there are no items
if (widget.contextMenuItems == null || widget.contextMenuItems!.isEmpty) {
// Don't trigger the context menu if the builder is null
if (widget.contextMenuBuilder == null) {
return;
}
@ -458,12 +458,14 @@ class _DesktopSelectionServiceWidgetState
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
final offset = details.localPosition + const Offset(10, 10) + baseOffset;
final contextMenu = OverlayEntry(
builder: (context) => ContextMenu(
position: offset,
editorState: editorState,
items: widget.contextMenuItems!,
onPressed: () => _clearContextMenu(),
),
builder: (_) =>
widget.contextMenuBuilder?.call(
context,
offset,
editorState,
() => _clearContextMenu(),
) ??
SizedBox.shrink(),
);
_contextMenuAreas.add(contextMenu);

View file

@ -11,7 +11,7 @@ class SelectionServiceWidget extends StatefulWidget {
this.cursorColor = const Color(0xFF00BCF0),
this.selectionColor = const Color.fromARGB(53, 111, 201, 231),
this.showMagnifier = true,
this.contextMenuItems,
this.contextMenuBuilder,
this.dropTargetStyle,
required this.child,
});
@ -19,7 +19,7 @@ class SelectionServiceWidget extends StatefulWidget {
final Widget child;
final Color cursorColor;
final Color selectionColor;
final List<List<ContextMenuItem>>? contextMenuItems;
final ContextMenuWidgetBuilder? contextMenuBuilder;
final AppFlowyDropTargetStyle? dropTargetStyle;
/// Show the magnifier or not.
@ -47,7 +47,7 @@ class _SelectionServiceWidgetState extends State<SelectionServiceWidget>
key: forwardKey,
cursorColor: widget.cursorColor,
selectionColor: widget.selectionColor,
contextMenuItems: widget.contextMenuItems,
contextMenuBuilder: widget.contextMenuBuilder,
dropTargetStyle:
widget.dropTargetStyle ?? const AppFlowyDropTargetStyle(),
child: widget.child,

View file

@ -1,6 +1,13 @@
import 'package:appflowy_editor/src/editor_state.dart';
import 'package:flutter/material.dart';
typedef ContextMenuWidgetBuilder = Widget Function(
BuildContext context,
Offset position,
EditorState editorState,
VoidCallback onPressed,
);
class ContextMenuItem {
ContextMenuItem({
required String Function() getName,