Fix web text field shortcuts (#79056)
This commit is contained in:
parent
dfc134dd9a
commit
971881c8c5
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb;
|
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -18,16 +18,6 @@ import 'theme.dart';
|
|||||||
|
|
||||||
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
|
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
|
||||||
|
|
||||||
// This is a temporary fix for: https://github.com/flutter/flutter/issues/79012
|
|
||||||
/// A map used to disable scrolling shortcuts in text fields.
|
|
||||||
final Map<LogicalKeySet, Intent> _webScrollShortcutOverrides = <LogicalKeySet, Intent>{
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.space): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowUp): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowLeft): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowRight): DoNothingAndStopPropagationIntent(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const TextStyle _kDefaultPlaceholderStyle = TextStyle(
|
const TextStyle _kDefaultPlaceholderStyle = TextStyle(
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: CupertinoColors.placeholderText,
|
color: CupertinoColors.placeholderText,
|
||||||
@ -1222,7 +1212,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final Widget child = Semantics(
|
return Semantics(
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
onTap: !enabled || widget.readOnly ? null : () {
|
onTap: !enabled || widget.readOnly ? null : () {
|
||||||
if (!controller.selection.isValid) {
|
if (!controller.selection.isValid) {
|
||||||
@ -1248,13 +1238,5 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (kIsWeb) {
|
|
||||||
return Shortcuts(
|
|
||||||
shortcuts: _webScrollShortcutOverrides,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,6 @@ import 'theme.dart';
|
|||||||
|
|
||||||
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
|
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
|
||||||
|
|
||||||
// This is a temporary fix for: https://github.com/flutter/flutter/issues/79012
|
|
||||||
/// A map used to disable scrolling shortcuts in text fields.
|
|
||||||
final Map<LogicalKeySet, Intent> _webScrollShortcutOverrides = <LogicalKeySet, Intent>{
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.space): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowUp): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowLeft): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowRight): DoNothingAndStopPropagationIntent(),
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Signature for the [TextField.buildCounter] callback.
|
/// Signature for the [TextField.buildCounter] callback.
|
||||||
typedef InputCounterWidgetBuilder = Widget? Function(
|
typedef InputCounterWidgetBuilder = Widget? Function(
|
||||||
/// The build context for the TextField.
|
/// The build context for the TextField.
|
||||||
@ -1311,7 +1301,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
|||||||
semanticsMaxValueLength = null;
|
semanticsMaxValueLength = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
child = MouseRegion(
|
return MouseRegion(
|
||||||
cursor: effectiveMouseCursor,
|
cursor: effectiveMouseCursor,
|
||||||
onEnter: (PointerEnterEvent event) => _handleHover(true),
|
onEnter: (PointerEnterEvent event) => _handleHover(true),
|
||||||
onExit: (PointerExitEvent event) => _handleHover(false),
|
onExit: (PointerExitEvent event) => _handleHover(false),
|
||||||
@ -1339,13 +1329,5 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (kIsWeb) {
|
|
||||||
return Shortcuts(
|
|
||||||
shortcuts: _webScrollShortcutOverrides,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ class _DoNothingAndStopPropagationTextAction extends TextEditingAction<DoNothing
|
|||||||
_DoNothingAndStopPropagationTextAction();
|
_DoNothingAndStopPropagationTextAction();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool consumesKey(Intent intent) => true;
|
bool consumesKey(Intent intent) => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(DoNothingAndStopPropagationTextIntent intent, [BuildContext? context]) {}
|
void invoke(DoNothingAndStopPropagationTextIntent intent, [BuildContext? context]) {}
|
||||||
|
@ -61,8 +61,7 @@ abstract class TextEditingAction<T extends Intent> extends ContextAction<T> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool isEnabled(T intent) {
|
bool isEnabled(T intent) {
|
||||||
// The Action is disabled if there is no focused TextEditingActionTarget, or
|
// The Action is disabled if there is no focused TextEditingActionTarget.
|
||||||
// if the platform is web, because web lets the browser handle text editing.
|
return textEditingActionTarget != null;
|
||||||
return !kIsWeb && textEditingActionTarget != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class TestLeftIntent extends Intent {}
|
||||||
|
class TestRightIntent extends Intent {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('DoNothingAndStopPropagationTextIntent', (WidgetTester tester) async {
|
||||||
|
bool leftCalled = false;
|
||||||
|
bool rightCalled = false;
|
||||||
|
final TextEditingController controller = TextEditingController(
|
||||||
|
text: 'blah1 blah2',
|
||||||
|
);
|
||||||
|
final FocusNode focusNodeTarget = FocusNode();
|
||||||
|
final FocusNode focusNodeNonTarget = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: ThemeData(),
|
||||||
|
home: Scaffold(
|
||||||
|
body: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Shortcuts(
|
||||||
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): TestLeftIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): TestRightIntent(),
|
||||||
|
},
|
||||||
|
child: Shortcuts(
|
||||||
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
},
|
||||||
|
child: Actions(
|
||||||
|
// These Actions intercept default Intents, set a flag that they
|
||||||
|
// were called, and then call through to the default Action.
|
||||||
|
actions: <Type, Action<Intent>>{
|
||||||
|
TestLeftIntent: CallbackAction<TestLeftIntent>(onInvoke: (Intent intent) {
|
||||||
|
leftCalled = true;
|
||||||
|
}),
|
||||||
|
TestRightIntent: CallbackAction<TestRightIntent>(onInvoke: (Intent intent) {
|
||||||
|
rightCalled = true;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
EditableText(
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNodeTarget,
|
||||||
|
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1!,
|
||||||
|
cursorColor: Colors.blue,
|
||||||
|
backgroundCursorColor: Colors.grey,
|
||||||
|
),
|
||||||
|
Focus(
|
||||||
|
focusNode: focusNodeNonTarget,
|
||||||
|
child: const Text('focusable'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Focus on the EditableText, which is a TextEditingActionTarget.
|
||||||
|
focusNodeTarget.requestFocus();
|
||||||
|
await tester.pump();
|
||||||
|
expect(focusNodeTarget.hasFocus, isTrue);
|
||||||
|
expect(focusNodeNonTarget.hasFocus, isFalse);
|
||||||
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
|
expect(controller.selection.baseOffset, 11);
|
||||||
|
|
||||||
|
// The left arrow key's Action is called.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
|
await tester.pump();
|
||||||
|
expect(leftCalled, isTrue);
|
||||||
|
expect(rightCalled, isFalse);
|
||||||
|
leftCalled = false;
|
||||||
|
|
||||||
|
// The right arrow key is blocked by DoNothingAndStopPropagationTextIntent.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
|
await tester.pump();
|
||||||
|
expect(rightCalled, isFalse);
|
||||||
|
expect(leftCalled, isFalse);
|
||||||
|
|
||||||
|
// Focus on the other node, which is not a TextEditingActionTarget.
|
||||||
|
focusNodeNonTarget.requestFocus();
|
||||||
|
await tester.pump();
|
||||||
|
expect(focusNodeTarget.hasFocus, isFalse);
|
||||||
|
expect(focusNodeNonTarget.hasFocus, isTrue);
|
||||||
|
|
||||||
|
// The left arrow key's Action is called as normal.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
|
await tester.pump();
|
||||||
|
expect(leftCalled, isTrue);
|
||||||
|
expect(rightCalled, isFalse);
|
||||||
|
leftCalled = false;
|
||||||
|
|
||||||
|
// The right arrow key's Action is also called. That's because
|
||||||
|
// DoNothingAndStopPropagationTextIntent only applies if a
|
||||||
|
// TextEditingActionTarget is currently focused.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
|
await tester.pump();
|
||||||
|
expect(leftCalled, isFalse);
|
||||||
|
expect(rightCalled, isTrue);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user