Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
533816d1
Unverified
Commit
533816d1
authored
May 12, 2022
by
chunhtai
Committed by
GitHub
May 12, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor web text editing shortcuts (#103377)
parent
1ea7d24e
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
387 additions
and
20 deletions
+387
-20
default_text_editing_shortcuts.dart
...utter/lib/src/widgets/default_text_editing_shortcuts.dart
+36
-15
debug_test.dart
packages/flutter/test/material/debug_test.dart
+8
-0
app_test.dart
packages/flutter/test/widgets/app_test.dart
+1
-1
clipboard_utils.dart
packages/flutter/test/widgets/clipboard_utils.dart
+4
-4
editable_text_shortcuts_test.dart
...es/flutter/test/widgets/editable_text_shortcuts_test.dart
+338
-0
No files found.
packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart
View file @
533816d1
...
...
@@ -6,11 +6,11 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/services.dart'
;
import
'actions.dart'
;
import
'framework.dart'
;
import
'shortcuts.dart'
;
import
'text_editing_intents.dart'
;
/// A [Shortcuts] widget with the shortcuts used for the default text editing
/// behavior.
/// A widget with the shortcuts used for the default text editing behavior.
///
/// 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
...
...
@@ -145,16 +145,16 @@ import 'text_editing_intents.dart';
/// See also:
///
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class
DefaultTextEditingShortcuts
extends
S
hortcuts
{
/// Creates a [Shortcuts] widget that provides the default text editing
class
DefaultTextEditingShortcuts
extends
S
tatelessWidget
{
/// Creates a [
DefaultTextEditing
Shortcuts] widget that provides the default text editing
/// shortcuts on the current platform.
DefaultTextEditingShortcuts
({
const
DefaultTextEditingShortcuts
({
super
.
key
,
required
super
.
child
,
})
:
super
(
debugLabel:
'<Default Text Editing Shortcuts>'
,
shortcuts:
_shortcuts
,
)
;
required
this
.
child
,
})
;
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
child
;
// These are shortcuts are shared between most platforms except macOS for it
// uses different modifier keys as the line/word modifier.
...
...
@@ -353,7 +353,7 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// Web handles its text selection natively and doesn't use any of these
// shortcuts in Flutter.
static
final
Map
<
ShortcutActivator
,
Intent
>
_webShortcuts
=
<
ShortcutActivator
,
Intent
>{
static
final
Map
<
ShortcutActivator
,
Intent
>
_web
DisablingText
Shortcuts
=
<
ShortcutActivator
,
Intent
>{
for
(
final
bool
pressShift
in
const
<
bool
>[
true
,
false
])
...<
SingleActivator
,
Intent
>{
SingleActivator
(
LogicalKeyboardKey
.
backspace
,
shift:
pressShift
):
const
DoNothingAndStopPropagationTextIntent
(),
...
...
@@ -412,10 +412,6 @@ class DefaultTextEditingShortcuts extends Shortcuts {
};
static
Map
<
ShortcutActivator
,
Intent
>
get
_shortcuts
{
if
(
kIsWeb
)
{
return
_webShortcuts
;
}
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
android
:
return
_androidShortcuts
;
...
...
@@ -431,4 +427,29 @@ class DefaultTextEditingShortcuts extends Shortcuts {
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
);
}
}
packages/flutter/test/material/debug_test.dart
View file @
533816d1
...
...
@@ -192,10 +192,18 @@ void main() {
' FocusTraversalGroup
\n
'
' _ActionsMarker
\n
'
' Actions
\n
'
'
${kIsWeb
? ' _ShortcutsMarker\n'
' Semantics\n'
' _FocusMarker\n'
' Focus\n'
' Shortcuts\n'
: ''}
'
' _ShortcutsMarker
\n
'
' Semantics
\n
'
' _FocusMarker
\n
'
' Focus
\n
'
' Shortcuts
\n
'
' DefaultTextEditingShortcuts
\n
'
' _ShortcutsMarker
\n
'
' Semantics
\n
'
...
...
packages/flutter/test/widgets/app_test.dart
View file @
533816d1
...
...
@@ -707,7 +707,7 @@ void main() {
expect
(
selectAllSpy
.
invoked
,
isTrue
);
expect
(
copySpy
.
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
);
...
...
packages/flutter/test/widgets/clipboard_utils.dart
View file @
533816d1
...
...
@@ -11,22 +11,22 @@ class MockClipboard {
final
bool
hasStringsThrows
;
dynamic
_
clipboardData
=
<
String
,
dynamic
>{
dynamic
clipboardData
=
<
String
,
dynamic
>{
'text'
:
null
,
};
Future
<
Object
?>
handleMethodCall
(
MethodCall
methodCall
)
async
{
switch
(
methodCall
.
method
)
{
case
'Clipboard.getData'
:
return
_
clipboardData
;
return
clipboardData
;
case
'Clipboard.hasStrings'
:
if
(
hasStringsThrows
)
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
?;
return
<
String
,
bool
>{
'value'
:
text
!=
null
&&
text
.
isNotEmpty
};
case
'Clipboard.setData'
:
_
clipboardData
=
methodCall
.
arguments
;
clipboardData
=
methodCall
.
arguments
;
break
;
}
return
null
;
...
...
packages/flutter/test/widgets/editable_text_shortcuts_test.dart
View file @
533816d1
...
...
@@ -7,6 +7,8 @@ import 'package:flutter/material.dart';
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'clipboard_utils.dart'
;
Future
<
void
>
sendKeyCombination
(
WidgetTester
tester
,
SingleActivator
activator
,
...
...
@@ -40,6 +42,18 @@ Iterable<SingleActivator> allModifierVariants(LogicalKeyboardKey trigger) {
}
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
MockClipboard
mockClipboard
=
MockClipboard
();
setUp
(()
async
{
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
platform
,
mockClipboard
.
handleMethodCall
);
await
Clipboard
.
setData
(
const
ClipboardData
(
text:
'empty'
));
});
tearDown
(()
{
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
platform
,
null
);
});
const
String
testText
=
'Now is the time for
\n
'
// 20
'all good people
\n
'
// 20 + 16 => 36
...
...
@@ -1797,4 +1811,328 @@ void main() {
));
},
variant:
macOSOnly
);
},
skip:
kIsWeb
);
// [intended] on web these keys are handled by the browser.
group
(
'Web does not accept'
,
()
{
final
TargetPlatformVariant
allExceptApple
=
TargetPlatformVariant
(
TargetPlatform
.
values
.
toSet
()..
removeAll
(<
TargetPlatform
>[
TargetPlatform
.
macOS
,
TargetPlatform
.
iOS
]));
const
TargetPlatformVariant
appleOnly
=
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
,
TargetPlatform
.
iOS
});
group
(
'macOS shortcuts'
,
()
{
testWidgets
(
'word modifier + arrowLeft'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
7
,
// Before the first "the"
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
alt:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
7
));
},
variant:
appleOnly
);
testWidgets
(
'word modifier + arrowRight'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
7
,
// Before the first "the"
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
alt:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
7
));
},
variant:
appleOnly
);
testWidgets
(
'line modifier + arrowLeft'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
24
,
// Before the "good".
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
24
,));
},
variant:
appleOnly
);
testWidgets
(
'line modifier + arrowRight'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
24
,
// Before the "good".
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
24
,
// Before the newline character.
));
},
variant:
appleOnly
);
testWidgets
(
'word modifier + arrow key movement'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
alt:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
));
controller
.
selection
=
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
);
await
tester
.
pump
();
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
alt:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
));
controller
.
selection
=
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
);
await
tester
.
pump
();
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
alt:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
));
// "good" to "come" is selected.
controller
.
selection
=
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
);
await
tester
.
pump
();
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
alt:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
));
},
variant:
appleOnly
);
testWidgets
(
'line modifier + arrow key movement'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
// "good" to "come" is selected.
controller
.
selection
=
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
));
// "good" to "come" is selected.
controller
.
selection
=
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
);
await
tester
.
pump
();
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
));
// "good" to "come" is selected.
controller
.
selection
=
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
);
await
tester
.
pump
();
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
24
,
extentOffset:
43
,
));
// "good" to "come" is selected.
controller
.
selection
=
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
);
await
tester
.
pump
();
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
43
,
extentOffset:
24
,
));
},
variant:
appleOnly
);
});
testWidgets
(
'vertical movement'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
for
(
final
SingleActivator
activator
in
allModifierVariants
(
LogicalKeyboardKey
.
arrowDown
))
{
await
sendKeyCombination
(
tester
,
activator
);
await
tester
.
pump
();
expect
(
controller
.
text
,
testText
);
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
),
reason:
activator
.
toString
(),
);
}
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'horizontal movement'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
for
(
final
SingleActivator
activator
in
allModifierVariants
(
LogicalKeyboardKey
.
arrowRight
))
{
await
sendKeyCombination
(
tester
,
activator
);
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
));
}
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'select all non apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyA
,
control:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
));
},
variant:
allExceptApple
);
testWidgets
(
'select all apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyA
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
));
},
variant:
appleOnly
);
testWidgets
(
'copy non apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyC
,
control:
true
));
await
tester
.
pump
();
final
Map
<
String
,
dynamic
>
clipboardData
=
mockClipboard
.
clipboardData
as
Map
<
String
,
dynamic
>;
expect
(
clipboardData
[
'text'
],
'empty'
);
},
variant:
allExceptApple
);
testWidgets
(
'copy apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyC
,
meta:
true
));
await
tester
.
pump
();
final
Map
<
String
,
dynamic
>
clipboardData
=
mockClipboard
.
clipboardData
as
Map
<
String
,
dynamic
>;
expect
(
clipboardData
[
'text'
],
'empty'
);
},
variant:
appleOnly
);
testWidgets
(
'cut non apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyX
,
control:
true
));
await
tester
.
pump
();
final
Map
<
String
,
dynamic
>
clipboardData
=
mockClipboard
.
clipboardData
as
Map
<
String
,
dynamic
>;
expect
(
clipboardData
[
'text'
],
'empty'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
,
));
},
variant:
allExceptApple
);
testWidgets
(
'cut apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyX
,
meta:
true
));
await
tester
.
pump
();
final
Map
<
String
,
dynamic
>
clipboardData
=
mockClipboard
.
clipboardData
as
Map
<
String
,
dynamic
>;
expect
(
clipboardData
[
'text'
],
'empty'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
,
));
},
variant:
appleOnly
);
testWidgets
(
'paste non apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
);
mockClipboard
.
clipboardData
=
<
String
,
dynamic
>{
'text'
:
'some text'
,
};
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyV
,
control:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
));
expect
(
controller
.
text
,
testText
);
},
variant:
allExceptApple
);
testWidgets
(
'paste apple'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
testText
;
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
);
mockClipboard
.
clipboardData
=
<
String
,
dynamic
>{
'text'
:
'some text'
,
};
await
tester
.
pumpWidget
(
buildEditableText
());
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyV
,
meta:
true
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
));
expect
(
controller
.
text
,
testText
);
},
variant:
appleOnly
);
},
skip:
!
kIsWeb
);
// [intended] specific tests target web.
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment