Skip to content

Commit

Permalink
[ExampleApp] Fix link popover (Resolves #1440) (#1443)
Browse files Browse the repository at this point in the history
  • Loading branch information
angelosilvestre authored Sep 21, 2023
1 parent 6789b77 commit f471c24
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 38 deletions.
42 changes: 29 additions & 13 deletions super_editor/example/lib/demos/example_editor/_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,23 @@ class _EditorToolbarState extends State<EditorToolbar> {
late FollowerBoundary _screenBoundary;

bool _showUrlField = false;
late FocusNode _popoverFocusNode;
late FocusNode _urlFocusNode;
AttributedTextEditingController? _urlController;
ImeAttributedTextEditingController? _urlController;

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

_toolbarAligner = CupertinoPopoverToolbarAligner(widget.editorViewportKey);

_popoverFocusNode = FocusNode();

_urlFocusNode = FocusNode();
_urlController = SingleLineAttributedTextEditingController(_applyLink) //
..text = AttributedText("https://");
_urlController =
ImeAttributedTextEditingController(controller: SingleLineAttributedTextEditingController(_applyLink)) //
..onPerformActionPressed = _onPerformAction
..text = AttributedText("https://");
}

@override
Expand All @@ -94,6 +99,7 @@ class _EditorToolbarState extends State<EditorToolbar> {
void dispose() {
_urlFocusNode.dispose();
_urlController!.dispose();
_popoverFocusNode.dispose();
super.dispose();
}

Expand Down Expand Up @@ -457,6 +463,12 @@ class _EditorToolbarState extends State<EditorToolbar> {
}
}

void _onPerformAction(TextInputAction action) {
if (action == TextInputAction.done) {
_applyLink();
}
}

@override
Widget build(BuildContext context) {
return BuildInOrder(
Expand All @@ -477,15 +489,19 @@ class _EditorToolbarState extends State<EditorToolbar> {
}

Widget _buildToolbars() {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildToolbar(),
if (_showUrlField) ...[
const SizedBox(height: 8),
_buildUrlField(),
return SuperEditorPopover(
popoverFocusNode: _popoverFocusNode,
editorFocusNode: widget.editorFocusNode,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildToolbar(),
if (_showUrlField) ...[
const SizedBox(height: 8),
_buildUrlField(),
],
],
],
),
);
}

Expand Down Expand Up @@ -619,9 +635,9 @@ class _EditorToolbarState extends State<EditorToolbar> {
child: Row(
children: [
Expanded(
child: FocusWithCustomParent(
child: Focus(
focusNode: _urlFocusNode,
parentFocusNode: widget.editorFocusNode,
parentNode: _popoverFocusNode,
// We use a SuperTextField instead of a TextField because TextField
// automatically re-parents its FocusNode, which causes #609. Flutter
// #106923 tracks the TextField issue.
Expand Down
12 changes: 6 additions & 6 deletions super_editor/example/lib/demos/in_the_lab/popover_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,12 @@ class _PopoverListState extends State<PopoverList> {

@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => !_focusNode.hasPrimaryFocus ? _focusNode.requestFocus() : null,
child: Focus(
focusNode: _focusNode,
parentNode: widget.editorFocusNode,
onKeyEvent: _onKeyEvent,
return SuperEditorPopover(
popoverFocusNode: _focusNode,
editorFocusNode: widget.editorFocusNode,
onKeyEvent: _onKeyEvent,
child: GestureDetector(
onTap: () => !_focusNode.hasPrimaryFocus ? _focusNode.requestFocus() : null,
child: ListenableBuilder(
listenable: _focusNode,
builder: (context, child) {
Expand Down
4 changes: 0 additions & 4 deletions super_editor/lib/src/core/edit_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class SuperEditorContext {
required DocumentLayout Function() getDocumentLayout,
required this.composer,
required this.scroller,
required this.hasPrimaryFocus,
required this.commonOps,
}) : _getDocumentLayout = getDocumentLayout;

Expand All @@ -49,9 +48,6 @@ class SuperEditorContext {
/// scrolling.
final DocumentScroller scroller;

/// Whether `SuperEditor` currently has primary focus.
final ValueListenable<bool> hasPrimaryFocus;

/// Common operations that can be executed to apply common, complex changes to
/// the document.
final CommonEditorOperations commonOps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,6 @@ ExecutionInstruction sendKeyEventToMacOs({
// For the full list of selectors handled by SuperEditor, see the MacOsSelectors class.
//
// This is needed for the interaction with the accent panel to work.

if (!editContext.hasPrimaryFocus.value) {
// SuperEditor has focus, but not primary focus. This can happen, for example,
// when an app displays a popover that takes primary focus. In this case, because
// SuperEditor no longer has primary focus, Flutter might intercept keys and do
// things we don't want, like move focus around when the user presses arrow keys.
// To prevent Flutter from doing things we don't want, in this case we run the
// standard key handlers instead of sending the signal to the OS.
return ExecutionInstruction.continueExecution;
}

return ExecutionInstruction.blocked;
}

Expand Down Expand Up @@ -517,7 +506,7 @@ ExecutionInstruction moveUpAndDownWithArrowKeys({
return ExecutionInstruction.continueExecution;
}

if (isWeb && (editContext.hasPrimaryFocus.value) && (editContext.composer.composingRegion.value != null)) {
if (isWeb && (editContext.composer.composingRegion.value != null)) {
// We are composing a character on web. It's possible that a native element is being displayed,
// like an emoji picker or a character selection panel.
// We need to let the OS handle the key so the user can navigate
Expand Down Expand Up @@ -578,7 +567,7 @@ ExecutionInstruction moveLeftAndRightWithArrowKeys({
return ExecutionInstruction.continueExecution;
}

if (isWeb && (editContext.hasPrimaryFocus.value) && (editContext.composer.composingRegion.value != null)) {
if (isWeb && (editContext.composer.composingRegion.value != null)) {
// We are composing a character on web. It's possible that a native element is being displayed,
// like an emoji picker or a character selection panel.
// We need to let the OS handle the key so the user can navigate
Expand Down
1 change: 0 additions & 1 deletion super_editor/lib/src/default_editor/super_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,6 @@ class SuperEditorState extends State<SuperEditor> {
composer: _composer,
getDocumentLayout: () => _docLayoutKey.currentState as DocumentLayout,
scroller: _scroller,
hasPrimaryFocus: _primaryFocusListener,
commonOps: CommonEditorOperations(
editor: widget.editor,
document: widget.document,
Expand Down
64 changes: 64 additions & 0 deletions super_editor/lib/src/infrastructure/popovers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:super_editor/src/infrastructure/focus.dart';
import 'package:super_editor/src/infrastructure/platforms/mac/mac_ime.dart';

/// A popover that shares focus with a `SuperEditor`.
///
/// Popovers often need to handle keyboard input, such as arrow keys for
/// selection movement. But `SuperEditor` also needs to continue handling
/// keyboard input to move the caret, enter text, etc. In such a case, the
/// popover has primary focus, and `SuperEditor` has non-primary focus.
/// Due to the way that Flutter's `Actions` system works, along with
/// Flutter's default response to certain key events, a few careful
/// adjustments need to be made so that a popover works with
/// `SuperEditor` as expected. This widget handles those adjustments.
///
/// Despite this widget being a "Super Editor popover", this widget can be
/// placed anywhere in the widget tree, so long as it's able to share focus
/// with `SuperEditor`.
///
/// This widget is purely logical - it doesn't impose any particular layout
/// or constraints. It's up to you whether this widget tightly hugs your
/// popover [child], or whether it expands to fill a space.
///
/// It's possible to create a `SuperEditor` popover without this widget.
/// This widget doesn't have any special access to `SuperEditor`
/// properties or behavior. But, if you choose to display a popover
/// without using this widget, you'll likely need to re-implement this
/// behavior to avoid unexpected user interaction results.
class SuperEditorPopover extends StatelessWidget {
const SuperEditorPopover({
super.key,
required this.popoverFocusNode,
required this.editorFocusNode,
this.onKeyEvent,
required this.child,
});

/// The [FocusNode] attached to the popover.
final FocusNode popoverFocusNode;

/// The [FocusNode] attached to the editor.
///
/// The [popoverFocusNode] will be reparented with this [FocusNode].
final FocusNode editorFocusNode;

/// Callback that notifies key events.
final FocusOnKeyEventCallback? onKeyEvent;

/// The popover to display.
final Widget child;

@override
Widget build(BuildContext context) {
return Actions(
actions: disabledMacIntents,
child: FocusWithCustomParent(
focusNode: popoverFocusNode,
parentFocusNode: editorFocusNode,
onKeyEvent: onKeyEvent,
child: child,
),
);
}
}
1 change: 1 addition & 0 deletions super_editor/lib/super_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export 'src/super_textfield/super_textfield.dart';
export 'src/infrastructure/touch_controls.dart';
export 'src/infrastructure/text_input.dart';
export 'src/infrastructure/viewport_size_reporting.dart';
export 'src/infrastructure/popovers.dart';

// Super Reader
export 'src/super_reader/read_only_document_android_touch_interactor.dart';
Expand Down
1 change: 0 additions & 1 deletion super_editor/test/super_editor/text_entry/text_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,6 @@ SuperEditorContext _createEditContext() {
getDocumentLayout: () => fakeLayout,
composer: composer,
scroller: FakeSuperEditorScroller(),
hasPrimaryFocus: ValueNotifier(false),
commonOps: CommonEditorOperations(
editor: documentEditor,
document: document,
Expand Down

0 comments on commit f471c24

Please sign in to comment.