diff --git a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart index 67263f054ca97..af1b8d6b00a0f 100644 --- a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart +++ b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart @@ -143,6 +143,57 @@ void main() { expect(input2.value, 'Text2'); }); + testWidgets('Jump between TextFormFields with tab key after CapsLock is' + 'activated', + (WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + + // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 + SystemChannels.textInput.setMockMethodCallHandler(null); + + // Focus on a TextFormField. + final Finder finder = find.byKey(const Key('input')); + expect(finder, findsOneWidget); + await tester.tap(find.byKey(const Key('input'))); + + // A native input element will be appended to the DOM. + final List nodeList = document.getElementsByTagName('input'); + expect(nodeList.length, equals(1)); + final InputElement input = + document.getElementsByTagName('input')[0] as InputElement; + + // Press and release CapsLock. + dispatchKeyboardEvent(input, 'keydown', { + 'key': 'CapsLock', + 'code': 'CapsLock', + 'bubbles': true, + 'cancelable': true, + }); + dispatchKeyboardEvent(input, 'keyup', { + 'key': 'CapsLock', + 'code': 'CapsLock', + 'bubbles': true, + 'cancelable': true, + }); + + // Press Tab. The focus should move to the next TextFormField. + dispatchKeyboardEvent(input, 'keydown', { + 'key': 'Tab', + 'code': 'Tab', + 'bubbles': true, + 'cancelable': true, + }); + + await tester.pumpAndSettle(); + + // A native input element for the next TextField should be attached to the + // DOM. + final InputElement input2 = + document.getElementsByTagName('input')[0] as InputElement; + expect(input2.value, 'Text2'); + }); + testWidgets('Read-only fields work', (WidgetTester tester) async { const String text = 'Lorem ipsum dolor sit amet'; app.main(); diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index c003dcfce6045..558e53f6f927a 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -116,6 +116,17 @@ class Keyboard { } _lastMetaState = _getMetaState(event); + if (event.type == 'keydown') { + // For lock modifiers _getMetaState won't report a metaState at keydown. + // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState. + if (event.key == 'CapsLock') { + _lastMetaState |= modifierCapsLock; + } else if (event.code == 'NumLock') { + _lastMetaState |= modifierNumLock; + } else if (event.key == 'ScrollLock') { + _lastMetaState |= modifierScrollLock; + } + } final Map eventData = { 'type': event.type, 'keymap': 'web', @@ -132,7 +143,6 @@ class Keyboard { switch (event.key) { case 'Tab': return true; - default: return false; } @@ -157,6 +167,9 @@ const int _modifierShift = 0x01; const int _modifierAlt = 0x02; const int _modifierControl = 0x04; const int _modifierMeta = 0x08; +const int modifierNumLock = 0x10; +const int modifierCapsLock = 0x20; +const int modifierScrollLock = 0x40; /// Creates a bitmask representing the meta state of the [event]. int _getMetaState(html.KeyboardEvent event) { @@ -173,8 +186,17 @@ int _getMetaState(html.KeyboardEvent event) { if (event.getModifierState('Meta')) { metaState |= _modifierMeta; } - // TODO: Re-enable lock key modifiers once there is support on Flutter - // Framework. https://github.com/flutter/flutter/issues/46718 + // See https://github.com/flutter/flutter/issues/66601 for why we don't + // set the ones below based on persistent state. + // if (event.getModifierState("CapsLock")) { + // metaState |= modifierCapsLock; + // } + // if (event.getModifierState("NumLock")) { + // metaState |= modifierNumLock; + // } + // if (event.getModifierState("ScrollLock")) { + // metaState |= modifierScrollLock; + // } return metaState; }