Skip to content

Commit cb1b65b

Browse files
committed
Add QuillFocusNode with support for keyboard enable/disable while preserving focus
1 parent 65da8cb commit cb1b65b

File tree

4 files changed

+91
-2
lines changed

4 files changed

+91
-2
lines changed

example/lib/main.dart

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,21 @@ class _HomePageState extends State<HomePage> {
6666
),
6767
));
6868
}();
69-
final FocusNode _editorFocusNode = FocusNode();
69+
final QuillFocusNode _editorFocusNode = QuillFocusNode();
7070
final ScrollController _editorScrollController = ScrollController();
7171

72+
late final ValueNotifier<bool> _editorFocusNodeEnabled =
73+
ValueNotifier(_editorFocusNode.keyboardEnabled);
74+
7275
@override
7376
void initState() {
7477
super.initState();
7578
// Load document
7679
_controller.document = Document.fromJson(kQuillDefaultSample);
80+
// Add listener to update the editor focus node enabled state.
81+
_editorFocusNodeEnabled.addListener(() {
82+
_editorFocusNode.keyboardEnabled = _editorFocusNodeEnabled.value;
83+
});
7784
}
7885

7986
@override
@@ -92,6 +99,38 @@ class _HomePageState extends State<HomePage> {
9299
debugPrint(jsonEncode(_controller.document.toDelta().toJson()));
93100
},
94101
),
102+
ValueListenableBuilder<bool>(
103+
valueListenable: _editorFocusNodeEnabled,
104+
builder: (context, hasFocus, child) {
105+
final isDark = Theme.of(context).brightness == Brightness.dark;
106+
final bool enabled = _editorFocusNodeEnabled.value;
107+
final Color backgroundColor = enabled
108+
? (isDark ? Colors.white : Colors.black)
109+
: Colors.transparent;
110+
final Color iconColor = enabled
111+
? (isDark ? Colors.black : Colors.white)
112+
: (isDark ? Colors.white : Colors.black);
113+
114+
return IconButton(
115+
icon: Container(
116+
decoration: BoxDecoration(
117+
color: backgroundColor,
118+
shape: BoxShape.circle,
119+
),
120+
padding: const EdgeInsets.all(6),
121+
child: Icon(
122+
Icons.edit,
123+
color: iconColor,
124+
),
125+
),
126+
tooltip: 'Toggle edit mode',
127+
onPressed: () {
128+
_editorFocusNodeEnabled.value =
129+
!_editorFocusNodeEnabled.value;
130+
},
131+
);
132+
},
133+
),
95134
],
96135
),
97136
body: SafeArea(

lib/flutter_quill.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export 'src/common/structs/image_url.dart';
55
export 'src/common/structs/offset_value.dart';
66
export 'src/common/structs/vertical_spacing.dart';
77
export 'src/common/utils/embeds.dart';
8+
export 'src/common/utils/quill_node_focus.dart';
89
export 'src/controller/quill_controller.dart';
910
export 'src/document/attribute.dart';
1011
export 'src/document/document.dart';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'package:flutter/widgets.dart';
2+
3+
class QuillFocusNode extends FocusNode {
4+
5+
QuillFocusNode({
6+
super.debugLabel,
7+
@Deprecated(
8+
'Use onKeyEvent instead. '
9+
'This feature was deprecated after v3.18.0-2.0.pre.',
10+
)
11+
super.onKey,
12+
super.onKeyEvent,
13+
super.skipTraversal,
14+
super.canRequestFocus,
15+
super.descendantsAreFocusable,
16+
super.descendantsAreTraversable,
17+
bool keyboardEnabled = true,
18+
}) : _keyboardEnabled = keyboardEnabled;
19+
20+
bool _keyboardEnabled = true;
21+
22+
bool get keyboardEnabled => _keyboardEnabled;
23+
24+
set keyboardEnabled(bool value) {
25+
if (_keyboardEnabled != value) {
26+
_keyboardEnabled = value;
27+
// Required otherwise the keyboard doesn't close/open automatically.
28+
unfocus();
29+
// Required otherwise requestFocus() does nothing and the previously caret
30+
// position is not shown.
31+
WidgetsBinding.instance.addPostFrameCallback((_) {
32+
requestFocus();
33+
});
34+
}
35+
}
36+
}

lib/src/editor/raw_editor/raw_editor_state_text_input_client_mixin.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:flutter/scheduler.dart';
77
import 'package:flutter/services.dart';
88
import 'package:meta/meta.dart';
99

10+
import '../../../flutter_quill.dart';
1011
import '../../delta/delta_diff.dart';
1112
import '../../document/document.dart';
1213
import '../editor.dart';
@@ -112,7 +113,19 @@ mixin RawEditorStateTextInputClientMixin on EditorState
112113
}
113114
_textInputConnection!.setEditingState(_lastKnownRemoteTextEditingValue!);
114115
}
115-
_textInputConnection!.show();
116+
// If the keyboard is disabled, only request focus for the editor's focus
117+
// node and do not open or show the software keyboard.
118+
if (_keyboardEnabled) {
119+
_textInputConnection!.show();
120+
}
121+
}
122+
123+
bool get _keyboardEnabled {
124+
if (widget.config.focusNode is QuillFocusNode) {
125+
final focusNode = widget.config.focusNode as QuillFocusNode;
126+
return focusNode.keyboardEnabled;
127+
}
128+
return true;
116129
}
117130

118131
void _updateComposingRectIfNeeded() {

0 commit comments

Comments
 (0)