chore: adjust context menu visibility logic (#1158)

* chore: default example context menu

* chore: adjust context menu visibility logic

* chore: dart analyzer
This commit is contained in:
Richard Shiue 2025-11-11 12:55:26 +08:00 • committed by GitHub
commit 15289103a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 69 additions and 29 deletions

View file

@ -2,7 +2,6 @@ import 'dart:math';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/flutter/overlay.dart';
import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart';
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:provider/provider.dart';
@ -125,8 +124,7 @@ class AppFlowyEditor extends StatefulWidget {
///
/// It will be shown when the user right click on the editor.
///
/// Defaults to a standard implementation containing
/// [standardContextMenuItems].
/// See the built-in [ContextMenu] implementation.
///
final ContextMenuWidgetBuilder? contextMenuBuilder;

View file

@ -286,15 +286,45 @@ class _DesktopSelectionServiceWidgetState
}
void _onSecondaryTapDown(TapDownDetails details) {
// if selection is null, or
// selection.isCollapsed and the selected node is TextNode.
// try to select the word.
final offset = details.globalPosition;
final selection = editorState.selectionNotifier.value;
if (selection == null ||
(selection.isCollapsed == true &&
currentSelectedNodes.first.delta != null)) {
_onDoubleTapDown(details);
final selectable = getNodeInOffset(offset)?.selectable;
if (selectable == null) {
clearSelection();
return;
}
final wordBoundary = selectable.getWordBoundaryInOffset(offset);
final Selection? newSelection;
final isSelectionCollapsed = selection == null || selection.isCollapsed;
final isWordBoundaryWithinSelection = !isSelectionCollapsed &&
wordBoundary != null &&
(wordBoundary.start.path > selection.start.path ||
wordBoundary.start.path == selection.start.path &&
wordBoundary.start.offset >= selection.start.offset) &&
(wordBoundary.end.path < selection.end.path ||
wordBoundary.end.path == selection.end.path &&
wordBoundary.end.offset <= selection.end.offset);
if (isWordBoundaryWithinSelection) {
newSelection = selection;
} else if (selectable.cursorStyle == CursorStyle.verticalLine) {
newSelection = wordBoundary;
} else {
newSelection = Selection(
start: selectable.start(),
end: selectable.end(),
);
}
editorState.updateSelectionWithReason(
newSelection,
extraInfo: {
selectionExtraInfoDisableToolbar: true,
},
);
_showContextMenu(details);
}
@ -436,25 +466,22 @@ class _DesktopSelectionServiceWidgetState
return;
}
final isHitSelectionAreas = currentSelection.value?.isCollapsed == true ||
selectionRects.any((element) {
const threshold = 20;
final scaledArea = Rect.fromCenter(
center: element.center,
width: element.width + threshold,
height: element.height + threshold,
);
return scaledArea.contains(details.globalPosition);
});
if (!isHitSelectionAreas) {
return;
}
// For now, only support the text node.
if (!currentSelectedNodes.every((element) => element.delta != null)) {
return;
}
final mask = OverlayEntry(
builder: (_) => Listener(
onPointerDown: (_) => _clearContextMenu(),
child: Container(
color: Colors.transparent,
),
),
);
_contextMenuAreas.add(mask);
Overlay.of(context, rootOverlay: true).insert(mask);
final baseOffset =
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
final offset = details.localPosition + const Offset(10, 10) + baseOffset;

View file

@ -135,16 +135,23 @@ class _FloatingToolbarState extends State<FloatingToolbar>
void _onSelectionChanged() {
final selection = editorState.selection;
final selectionType = editorState.selectionType;
final disableToolbar =
editorState.selectionExtraInfo?[selectionExtraInfoDisableToolbar] ==
true;
if (disableToolbar) {
_clear();
}
if (lastSelection == selection) return;
lastSelection = selection;
if (selection == null ||
selection.isCollapsed ||
selectionType == SelectionType.block ||
editorState.selectionExtraInfo?[selectionExtraInfoDisableToolbar] ==
true) {
selectionType == SelectionType.block) {
_clear();
} else {
} else if (!disableToolbar) {
// uses debounce to avoid the computing the rects too frequently.
_showAfterDelay(
duration: const Duration(milliseconds: 200),