Unverified Commit 533816d1 authored by chunhtai's avatar chunhtai Committed by GitHub

Refactor web text editing shortcuts (#103377)

parent 1ea7d24e
...@@ -6,11 +6,11 @@ import 'package:flutter/foundation.dart'; ...@@ -6,11 +6,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'actions.dart'; import 'actions.dart';
import 'framework.dart';
import 'shortcuts.dart'; import 'shortcuts.dart';
import 'text_editing_intents.dart'; import 'text_editing_intents.dart';
/// A [Shortcuts] widget with the shortcuts used for the default text editing /// A widget with the shortcuts used for the default text editing behavior.
/// behavior.
/// ///
/// This default behavior can be overridden by placing a [Shortcuts] widget /// This default behavior can be overridden by placing a [Shortcuts] widget
/// lower in the widget tree than this. See the [Action] class for an example /// lower in the widget tree than this. See the [Action] class for an example
...@@ -145,16 +145,16 @@ import 'text_editing_intents.dart'; ...@@ -145,16 +145,16 @@ import 'text_editing_intents.dart';
/// See also: /// See also:
/// ///
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts. /// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class DefaultTextEditingShortcuts extends Shortcuts { class DefaultTextEditingShortcuts extends StatelessWidget {
/// Creates a [Shortcuts] widget that provides the default text editing /// Creates a [DefaultTextEditingShortcuts] widget that provides the default text editing
/// shortcuts on the current platform. /// shortcuts on the current platform.
DefaultTextEditingShortcuts({ const DefaultTextEditingShortcuts({
super.key, super.key,
required super.child, required this.child,
}) : super( });
debugLabel: '<Default Text Editing Shortcuts>',
shortcuts: _shortcuts, /// {@macro flutter.widgets.ProxyWidget.child}
); final Widget child;
// These are shortcuts are shared between most platforms except macOS for it // These are shortcuts are shared between most platforms except macOS for it
// uses different modifier keys as the line/word modifier. // uses different modifier keys as the line/word modifier.
...@@ -353,7 +353,7 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -353,7 +353,7 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// Web handles its text selection natively and doesn't use any of these // Web handles its text selection natively and doesn't use any of these
// shortcuts in Flutter. // shortcuts in Flutter.
static final Map<ShortcutActivator, Intent> _webShortcuts = <ShortcutActivator, Intent>{ static final Map<ShortcutActivator, Intent> _webDisablingTextShortcuts = <ShortcutActivator, Intent>{
for (final bool pressShift in const <bool>[true, false]) for (final bool pressShift in const <bool>[true, false])
...<SingleActivator, Intent>{ ...<SingleActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace, shift: pressShift): const DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.backspace, shift: pressShift): const DoNothingAndStopPropagationTextIntent(),
...@@ -412,10 +412,6 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -412,10 +412,6 @@ class DefaultTextEditingShortcuts extends Shortcuts {
}; };
static Map<ShortcutActivator, Intent> get _shortcuts { static Map<ShortcutActivator, Intent> get _shortcuts {
if (kIsWeb) {
return _webShortcuts;
}
switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
case TargetPlatform.android: case TargetPlatform.android:
return _androidShortcuts; return _androidShortcuts;
...@@ -431,4 +427,29 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -431,4 +427,29 @@ class DefaultTextEditingShortcuts extends Shortcuts {
return _windowsShortcuts; return _windowsShortcuts;
} }
} }
@override
Widget build(BuildContext context) {
Widget result = child;
if (kIsWeb) {
// On the web, these shortcuts make sure of the following:
//
// 1. Shortcuts fired when an EditableText is focused are ignored and
// forwarded to the browser by the EditableText's Actions, because it
// maps DoNothingAndStopPropagationTextIntent to DoNothingAction.
// 2. Shortcuts fired when no EditableText is focused will still trigger
// _shortcuts assuming DoNothingAndStopPropagationTextIntent is
// unhandled elsewhere.
result = Shortcuts(
debugLabel: '<Web Disabling Text Editing Shortcuts>',
shortcuts: _webDisablingTextShortcuts,
child: result
);
}
return Shortcuts(
debugLabel: '<Default Text Editing Shortcuts>',
shortcuts: _shortcuts,
child: result
);
}
} }
...@@ -192,10 +192,18 @@ void main() { ...@@ -192,10 +192,18 @@ void main() {
' FocusTraversalGroup\n' ' FocusTraversalGroup\n'
' _ActionsMarker\n' ' _ActionsMarker\n'
' Actions\n' ' Actions\n'
'${kIsWeb
? ' _ShortcutsMarker\n'
' Semantics\n'
' _FocusMarker\n'
' Focus\n'
' Shortcuts\n'
: ''}'
' _ShortcutsMarker\n' ' _ShortcutsMarker\n'
' Semantics\n' ' Semantics\n'
' _FocusMarker\n' ' _FocusMarker\n'
' Focus\n' ' Focus\n'
' Shortcuts\n'
' DefaultTextEditingShortcuts\n' ' DefaultTextEditingShortcuts\n'
' _ShortcutsMarker\n' ' _ShortcutsMarker\n'
' Semantics\n' ' Semantics\n'
......
...@@ -707,7 +707,7 @@ void main() { ...@@ -707,7 +707,7 @@ void main() {
expect(selectAllSpy.invoked, isTrue); expect(selectAllSpy.invoked, isTrue);
expect(copySpy.invoked, isTrue); expect(copySpy.invoked, isTrue);
expect(pasteSpy.invoked, isTrue); expect(pasteSpy.invoked, isTrue);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }), skip: kIsWeb); // [intended] Web uses a different set of shortcuts. }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
} }
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation); typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
......
...@@ -11,22 +11,22 @@ class MockClipboard { ...@@ -11,22 +11,22 @@ class MockClipboard {
final bool hasStringsThrows; final bool hasStringsThrows;
dynamic _clipboardData = <String, dynamic>{ dynamic clipboardData = <String, dynamic>{
'text': null, 'text': null,
}; };
Future<Object?> handleMethodCall(MethodCall methodCall) async { Future<Object?> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) { switch (methodCall.method) {
case 'Clipboard.getData': case 'Clipboard.getData':
return _clipboardData; return clipboardData;
case 'Clipboard.hasStrings': case 'Clipboard.hasStrings':
if (hasStringsThrows) if (hasStringsThrows)
throw Exception(); throw Exception();
final Map<String, dynamic>? clipboardDataMap = _clipboardData as Map<String, dynamic>?; final Map<String, dynamic>? clipboardDataMap = clipboardData as Map<String, dynamic>?;
final String? text = clipboardDataMap?['text'] as String?; final String? text = clipboardDataMap?['text'] as String?;
return <String, bool>{'value': text != null && text.isNotEmpty}; return <String, bool>{'value': text != null && text.isNotEmpty};
case 'Clipboard.setData': case 'Clipboard.setData':
_clipboardData = methodCall.arguments; clipboardData = methodCall.arguments;
break; break;
} }
return null; return null;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment