diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index b131a08fa153f..1af89de167c07 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -512,6 +512,7 @@ class InputConfiguration { const TextCapitalizationConfig.defaultCapitalization(), this.autofill, this.autofillGroup, + this.forceSubmitOnFocusLost = false, }); InputConfiguration.fromFrameworkMessage( @@ -523,6 +524,7 @@ class InputConfiguration { inputAction = flutterInputConfiguration['inputAction'] ?? 'TextInputAction.done', obscureText = flutterInputConfiguration['obscureText'] ?? false, + forceSubmitOnFocusLost = flutterInputConfiguration['forceSubmitOnFocusLost'] ?? false, readOnly = flutterInputConfiguration['readOnly'] ?? false, autocorrect = flutterInputConfiguration['autocorrect'] ?? true, textCapitalization = TextCapitalizationConfig.fromInputConfiguration( @@ -550,6 +552,22 @@ class InputConfiguration { /// Whether to hide the text being edited. final bool obscureText; + /// Previously, Flutter for Web behaved like a web page. If user clicked on an + /// area other than the input field itself, the input field was blurred, thus + /// the connection is closed. + /// This behavior was changed for Desktop Browsers. + /// https://github.com/flutter/engine/pull/18743 + /// + /// Now only, pressing enter or tab changes the focus of the input fields. + /// + /// The created a regression for applications that relied on the old beharior + /// https://github.com/flutter/flutter/issues/64245 + /// + /// This flag provides a workaround to the regression allowing developers to + /// optionally force the connection to close on blur, as suggested in the comments + /// https://github.com/flutter/flutter/issues/64245#issuecomment-681815149 + final bool forceSubmitOnFocusLost; + /// Whether to enable autocorrection. /// /// Definition of autocorrect can be found in: @@ -838,10 +856,29 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy { _subscriptions.add(html.document.onSelectionChange.listen(_handleChange)); - // Refocus on the domElement after blur, so that user can keep editing the - // text field. + // The behavior for blur in DOM elements changes depending on the reason of + // blur: + // + // (1) By default we refocus on the domElement after blur, so that user can + // keep editing the text field. + // + // (2) BUT if the blur is triggered due to interaction with another + // element on the page AND we explicitly force the connection to close on + // blur, the current text connection is obsolete so connection close request + // is send to Flutter. + // + // See [HybridTextEditing.sendTextConnectionClosedToFlutterIfAny]. + // + // In order to detect between these two cases, after a blur event is + // triggered [domRenderer.windowHasFocus] method which checks the window + // focus is called. _subscriptions.add(domElement.onBlur.listen((_) { - domElement.focus(); + bool windowHasFocus = domRenderer.windowHasFocus ?? false; + if (windowHasFocus && _inputConfiguration.forceSubmitOnFocusLost) { + owner.sendTextConnectionClosedToFrameworkIfAny(); + } else { + domElement.focus(); + } })); preventDefaultForMouseEvents(); @@ -1259,10 +1296,23 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy { // enough for covering "Select All" functionality. _subscriptions.add(domElement.onSelect.listen(_handleChange)); - // Refocus on the domElement after blur, so that user can keep editing the - // text field. + + // For Firefox, we also use the same approach as the parent class. + // + // The different part is, in Firefox, we are not able to get correct value + // when we check the window focus like [domRendered.windowHasFocus]. + // + // However [document.activeElement] always equals to [domElement] if the + // user goes to another tab, minimizes the browser or opens the dev tools. + // Hence [document.activeElement] is checked in this listener. _subscriptions.add(domElement.onBlur.listen((_) { - _postponeFocus(); + html.Element? activeElement = html.document.activeElement; + bool domElementIsActive = activeElement != domElement; + if (domElementIsActive && _inputConfiguration.forceSubmitOnFocusLost) { + owner.sendTextConnectionClosedToFrameworkIfAny(); + } else { + _postponeFocus(); + } })); preventDefaultForMouseEvents(); diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/text_editing_test.dart index efcf24df89585..c0a88e705debd 100644 --- a/lib/web_ui/test/text_editing_test.dart +++ b/lib/web_ui/test/text_editing_test.dart @@ -473,6 +473,37 @@ void testMain() { // TODO(nurhan): https://github.com/flutter/flutter/issues/50769 skip: browserEngine == BrowserEngine.edge); + test('Does not re-acquire focus when forced', () { + editingElement = + SemanticsTextEditingStrategy( + SemanticsObject(5, null), HybridTextEditing(), testInputElement); + final InputConfiguration config = InputConfiguration( + inputType: EngineInputType.text, + forceSubmitOnFocusLost: true, + ); + + expect(document.activeElement, document.body); + + document.body.append(testInputElement); + editingElement.enable( + config, + onChange: trackEditingState, + onAction: trackInputAction, + ); + expect(document.activeElement, testInputElement); + + // The input should lose focus now.. + editingElement.domElement.blur(); + expect(document.activeElement, document.body); + + editingElement.disable(); + }, + // TODO(nurhan): https://github.com/flutter/flutter/issues/50590 + // TODO(nurhan): https://github.com/flutter/flutter/issues/50769 + skip: (browserEngine == BrowserEngine.webkit || + browserEngine == BrowserEngine.edge || + browserEngine == BrowserEngine.firefox)); + test('Does not dispose and recreate dom elements in persistent mode', () { editingElement = SemanticsTextEditingStrategy( SemanticsObject(5, null), HybridTextEditing(), testInputElement); @@ -2274,6 +2305,7 @@ Map createFlutterConfig( String inputType, { bool readOnly = false, bool obscureText = false, + bool forceSubmitOnFocusLost = false, bool autocorrect = true, String textCapitalization = 'TextCapitalization.none', String inputAction, @@ -2288,6 +2320,7 @@ Map createFlutterConfig( }, 'readOnly': readOnly, 'obscureText': obscureText, + 'forceSubmitOnFocusLost': forceSubmitOnFocusLost, 'autocorrect': autocorrect, 'inputAction': inputAction ?? 'TextInputAction.done', 'textCapitalization': textCapitalization,