Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `onKeyPressed` in `QuillEditorConfig` to customize key press handling in the editor [#2368](https://github.com/singerdmx/flutter-quill/pull/2368).

## [11.0.0-dev.10] - 2024-11-10

### Changed
Expand Down
33 changes: 33 additions & 0 deletions lib/src/editor/config/editor_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart' show experimental;

import '../../document/nodes/node.dart';
import '../../toolbar/theme/quill_dialog_theme.dart';
import '../embed/embed_editor_builder.dart';
import '../raw_editor/builders/leading_block_builder.dart';
Expand Down Expand Up @@ -51,6 +52,7 @@ class QuillEditorConfig {
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
@experimental this.onKeyPressed,
this.enableAlwaysIndentOnTab = false,
this.embedBuilders,
this.unknownEmbedBuilder,
Expand Down Expand Up @@ -135,6 +137,35 @@ class QuillEditorConfig {
@experimental
final List<SpaceShortcutEvent> spaceShortcutEvents;

/// A handler for keys that are pressed when the editor is focused.
///
/// This feature is supported on **desktop devices only**.
///
/// # Example:
/// To prevent the user from removing any **Embed Object**, try:
///
///```dart
///onKeyPressed: (event, node) {
/// if (event.logicalKey == LogicalKeyboardKey.backspace &&
/// (node is Line || node is Block)) {
/// // Use [DeltaIterator] to jump directly to the position before the current.
/// final iterator = DeltaIterator(_controller.document.toDelta())
/// ..skip(_controller.selection.baseOffset - 1);
/// // Get the [Operation] where the caret is on
/// final cur = iterator.next();
/// final isOperationWithEmbed = cur.data is! String && cur.data != null;
/// if (isOperationWithEmbed) {
/// // Ignore this [KeyEvent] to prevent the user from removing the [Embed Object].
/// return KeyEventResult.handled;
/// }
/// }
/// // Apply custom logic or return null to use default events
/// return null;
///},
///```
@experimental
final KeyEventResult? Function(KeyEvent event, Node? node)? onKeyPressed;

/// Override [readOnly] for checkbox.
///
/// When this is set to `false`, the checkbox can be checked
Expand Down Expand Up @@ -442,6 +473,7 @@ class QuillEditorConfig {
bool? autoFocus,
bool? onTapOutsideEnabled,
Function(PointerDownEvent event, FocusNode focusNode)? onTapOutside,
KeyEventResult? Function(KeyEvent event, Node? node)? onKeyPressed,
bool? showCursor,
bool? paintCursorAboveText,
MouseCursor? readOnlyMouseCursor,
Expand Down Expand Up @@ -491,6 +523,7 @@ class QuillEditorConfig {
checkBoxReadOnly: checkBoxReadOnly ?? this.checkBoxReadOnly,
disableClipboard: disableClipboard ?? this.disableClipboard,
scrollable: scrollable ?? this.scrollable,
onKeyPressed: onKeyPressed ?? this.onKeyPressed,
scrollBottomInset: scrollBottomInset ?? this.scrollBottomInset,
enableAlwaysIndentOnTab:
enableAlwaysIndentOnTab ?? this.enableAlwaysIndentOnTab,
Expand Down
1 change: 1 addition & 0 deletions lib/src/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ class QuillEditorState extends State<QuillEditor>
config: QuillRawEditorConfig(
characterShortcutEvents: widget.config.characterShortcutEvents,
spaceShortcutEvents: widget.config.spaceShortcutEvents,
onKeyPressed: widget.config.onKeyPressed,
customLeadingBuilder: widget.config.customLeadingBlockBuilder,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
Expand Down
32 changes: 32 additions & 0 deletions lib/src/editor/raw_editor/config/raw_editor_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

import '../../../document/nodes/node.dart';
import '../../../editor/embed/embed_editor_builder.dart';
import '../../../editor/raw_editor/raw_editor.dart';
import '../../../editor/raw_editor/raw_editor_state.dart';
Expand All @@ -27,6 +28,7 @@ class QuillRawEditorConfig {
required this.autoFocus,
required this.characterShortcutEvents,
required this.spaceShortcutEvents,
@experimental this.onKeyPressed,
this.showCursor = true,
this.scrollable = true,
this.padding = EdgeInsets.zero,
Expand Down Expand Up @@ -86,6 +88,7 @@ class QuillRawEditorConfig {
///
/// - Web
/// - Desktop
///
/// ### Example
///```dart
/// // you can get also the default implemented shortcuts
Expand Down Expand Up @@ -123,6 +126,35 @@ class QuillRawEditorConfig {
///```
final List<SpaceShortcutEvent> spaceShortcutEvents;

/// A handler for keys that are pressed when the editor is focused.
///
/// This feature is supported on **desktop devices only**.
///
/// # Example:
/// To prevent the user from removing any **Embed Object**, try:
///
///```dart
///onKeyPressed: (event, node) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Space between the onKeyPressed and the docs comment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Space between the onKeyPressed and the docs comment.

It's purely a matter of visual taste. I prefer it this way. It's clear that in the end in Markdown this is irrelevant and will remove any spacing I put in.

Copy link
Collaborator Author

@EchoEllet EchoEllet Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's though we have different tastes, and that makes the project docs style slightly inconsistent, I'm trying to use something that's commonly used in the project and known by most developers to make it easier to adapt.

This style is also quite common. It's used in Flutter (example code) and Dart (related Effective Dart section).

It's clear that in the end in Markdown this is irrelevant and will remove any spacing I put in.

It's irrelevant to the final result but not necessarily to the project code.

The if check code below has 3 spaces which might not make it formatted properly.

/// if (event.logicalKey == LogicalKeyboardKey.backspace &&
/// (node is Line || node is Block)) {
/// // Use [DeltaIterator] to jump directly to the position before the current.
/// final iterator = DeltaIterator(_controller.document.toDelta())
/// ..skip(_controller.selection.baseOffset - 1);
/// // Get the [Operation] where the caret is on
/// final cur = iterator.next();
/// final isOperationWithEmbed = cur.data is! String && cur.data != null;
/// if (isOperationWithEmbed) {
/// // Ignore this [KeyEvent] to prevent the user from removing the [Embed Object].
/// return KeyEventResult.handled;
/// }
/// }
/// // Apply custom logic or return null to use default events
/// return null;
///},
///```
@experimental
final KeyEventResult? Function(KeyEvent event, Node? node)? onKeyPressed;

/// Additional space around the editor contents.
final EdgeInsetsGeometry padding;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../../document/document.dart';
import '../../../document/nodes/block.dart';
import '../../../document/nodes/leaf.dart' as leaf;
import '../../../document/nodes/line.dart';
import '../../../document/nodes/node.dart';
import '../../widgets/keyboard_listener.dart';
import '../config/events/character_shortcuts_events.dart';
import '../config/events/space_shortcut_events.dart';
Expand All @@ -27,6 +28,7 @@ class EditorKeyboardShortcuts extends StatelessWidget {
required this.enableAlwaysIndentOnTab,
required this.characterEvents,
required this.spaceEvents,
this.onKeyPressed,
this.customShortcuts,
this.customActions,
super.key,
Expand All @@ -35,6 +37,8 @@ class EditorKeyboardShortcuts extends StatelessWidget {
final bool readOnly;
final bool enableAlwaysIndentOnTab;
final QuillController controller;
@experimental
final KeyEventResult? Function(KeyEvent event, Node? node)? onKeyPressed;
final List<CharacterShortcutEvent> characterEvents;
final List<SpaceShortcutEvent> spaceEvents;
final Map<ShortcutActivator, Intent>? customShortcuts;
Expand Down Expand Up @@ -74,6 +78,14 @@ class EditorKeyboardShortcuts extends StatelessWidget {
}

KeyEventResult _onKeyEvent(node, KeyEvent event) {
final onKey = onKeyPressed;
if (onKey != null) {
// Find the current node the user is on.
final node =
controller.document.queryChild(controller.selection.baseOffset).node;
final result = onKey.call(event, node);
if (result != null) return result;
}
// Don't handle key if there is a meta key pressed.
if (HardwareKeyboard.instance.isAltPressed ||
HardwareKeyboard.instance.isControlPressed ||
Expand Down
1 change: 1 addition & 0 deletions lib/src/editor/raw_editor/raw_editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ class QuillRawEditorState extends EditorState
data: _styles!,
child: EditorKeyboardShortcuts(
actions: _shortcutActionsManager.actions,
onKeyPressed: widget.config.onKeyPressed,
characterEvents: widget.config.characterShortcutEvents,
spaceEvents: widget.config.spaceShortcutEvents,
constraints: constraints,
Expand Down
Loading