[Web] Fix IME and selection by syncing more text styles#180436
[Web] Fix IME and selection by syncing more text styles#180436auto-submit[bot] merged 18 commits intoflutter:masterfrom
Conversation
6d69ece to
2eb268c
Compare
a4bc2b3 to
c6bb69c
Compare
0583f38 to
b36bb05
Compare
| assert(isEnabled); | ||
| // Preserve the internal scroll position. | ||
| if (geometry != null && lastEditingState != null) { | ||
| final key = '${geometry!.hashCode}_${lastEditingState!.text.hashCode}'; |
There was a problem hiding this comment.
I'm somewhat concerned about this key, it doesn't seem like a robust solution. But I don't have any better ideas at the moment.
There was a problem hiding this comment.
Yes, this key approach has edge cases.
I wanted to keep this PR scope small first. So I made this solution closed within the flutter engine side.
Syncing scrollTop from flutter framework when scroll occurs would solve this problem better. But that would add another breaking change discussion.
If the team prefers the more robust approach, I can work on a follow-up PR 👍
| @Deprecated( | ||
| 'Use setStyleWithMetrics instead. ' | ||
| 'This feature was deprecated after v3.40.0-0.2.pre.', | ||
| ) |
There was a problem hiding this comment.
@loic-sharma any thoughts on doing this vs adding the parameters to the existing setStyle(...) method without deprecating it? The latter may be considered a breaking change if users are expected to extend/implement TextInputConnection?
There was a problem hiding this comment.
It looks like people do implement TextInputConnection (1, 2).
The current approach does avoid a breaking change, but it prevents users from overriding setStyleWithMetrics. I don't think we should do this approach since people do override setStyle (example).
What do y'all think of this idea:
-
Introduce a new
TextInputStyleclass, similar to the existingTextInputConfiguration:Something like:
final class TextInputStyle { const TextInputStyle( ... ); final String? fontFamily; final double? fontSize; final FontWeight? fontWeight; final TextDirection textDirection; final TextAlign textAlign; final double? letterSpacing; final double? wordSpacing; final double? lineHeight; }
Notice
TextInputStyleisfinal. -
Add a new
TextInputConnection.updateStyle(TextInputStyle style)method that callsTextInput._instance._setStyle. -
Deprecate
TextInputConnection.setStyle. -
Do all the stuff in https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
This is still a breaking change - if your package implements TextInputConnection, you'll need to add a new updateStyle method.
However, this is a one-time breaking change. In the future, we'll be able to add more members to TextInputStyle without it being a breaking change.
What do y'all think?
There was a problem hiding this comment.
cc @Renzo-Olivares too, would love your thoughts as well
There was a problem hiding this comment.
Chatted offline but I agree with the TextInputStyle approach!
| textAlign: widget.textAlign, | ||
| letterSpacing: _style.letterSpacing, | ||
| wordSpacing: _style.wordSpacing, | ||
| lineHeight: renderEditable.preferredLineHeight, |
There was a problem hiding this comment.
I suspect this value can be stale. I believe RenderEditable.preferredLineHeight isn't updated until _Editable rebuilds, which happens after EditableTextState.didUpdateWidget is called.
| textAlign: widget.textAlign, | ||
| letterSpacing: _style.letterSpacing, | ||
| wordSpacing: _style.wordSpacing, | ||
| lineHeight: renderEditable.preferredLineHeight, |
There was a problem hiding this comment.
Just like these take the MediaQuery.boldTextOf account, these likely need to take the following into account:
MediaQuery.maybeLineHeightScaleFactorOverrideOf(context);MediaQuery.maybeLetterSpacingOverrideOf(context);MediaQuery.maybeWordSpacingOverrideOf(context);
|
Hello, I'll mark this as a draft until the feedback is addressed (for example #180436 (comment) and #180436 (comment)). Please feel free to mark this as ready for review when ready! Thanks for the wonderful contribution :) |
|
Thanks for the heads up! I'll make the PR ready once I've fixed it and it passes tests excluding customer testing. |
There was a problem hiding this comment.
Code Review
This pull request introduces two main fixes for text editing on the web. First, it improves text metrics synchronization by passing letter-spacing, word-spacing, and line-height from the framework to the engine. This is achieved by introducing a new TextInputStyle class to encapsulate styling information, which is a nice refactoring. Second, it fixes an issue with scroll position loss in text fields by preserving and restoring the scrollTop of the DOM element across enable/disable cycles. The changes are well-tested with new and updated tests. My review includes a couple of suggestions to improve code maintainability by reducing duplication and using a more robust method for map key generation. Overall, this is a great set of improvements.
| EditableTextGeometry? geometry; | ||
|
|
||
| /// The scroll top of the editable text on the page. | ||
| final Map<String, double> _preservedScrollTops = <String, double>{}; |
There was a problem hiding this comment.
Using string interpolation of hash codes to generate a map key can be fragile and less efficient than using an integer hash. Consider using Object.hash to combine the hash codes of geometry and lastEditingState!.text.
This would involve changing this map to Map<int, double> and then updating the key generation in both disable() (line 1460) and enable() (line 1669) methods to use Object.hash(geometry, lastEditingState!.text). This approach is more robust against hash collisions and provides better performance for map lookups.
| final Map<String, double> _preservedScrollTops = <String, double>{}; | |
| final Map<int, double> _preservedScrollTops = <int, double>{}; |
| _textInputConnection!.updateStyle( | ||
| TextInputStyle( | ||
| fontFamily: _style.fontFamily, | ||
| fontSize: _style.fontSize, | ||
| fontWeight: _style.fontWeight, | ||
| textDirection: _textDirection, | ||
| textAlign: widget.textAlign, | ||
| letterSpacing: letterSpacingOverride ?? _style.letterSpacing, | ||
| wordSpacing: wordSpacingOverride ?? _style.wordSpacing, | ||
| // preferredLineHeight already includes lineHeightScaleFactor from | ||
| // _OverridingTextStyleTextSpanUtils.applyTextSpacingOverrides. | ||
| lineHeight: renderEditable.preferredLineHeight, | ||
| ), | ||
| ); |
There was a problem hiding this comment.
This logic for creating a TextInputStyle is duplicated in a few places (_openInputConnection, _restartConnectionIfNeeded). To improve maintainability, consider extracting it into a helper method.
For example:
TextInputStyle _getTextInputStyle() {
final double? letterSpacingOverride = MediaQuery.maybeLetterSpacingOverrideOf(context);
final double? wordSpacingOverride = MediaQuery.maybeWordSpacingOverrideOf(context);
return TextInputStyle(
fontFamily: _style.fontFamily,
fontSize: _style.fontSize,
fontWeight: _style.fontWeight,
textDirection: _textDirection,
textAlign: widget.textAlign,
letterSpacing: letterSpacingOverride ?? _style.letterSpacing,
wordSpacing: wordSpacingOverride ?? _style.wordSpacing,
lineHeight: renderEditable.preferredLineHeight,
);
}This would simplify this block and others to _textInputConnection!.updateStyle(_getTextInputStyle());.
| 'textDirectionIndex': style.textDirection.index, | ||
| 'letterSpacing': style.letterSpacing, | ||
| 'wordSpacing': style.wordSpacing, | ||
| 'lineHeight': style.lineHeight, |
There was a problem hiding this comment.
Could you move this logic to TextStyle.toJson, similar to TextInputConfiguration.toJson?
Roll Flutter from 9bda20a11f1e to 6e4a481bdf27 (103 revisions) flutter/flutter@9bda20a...6e4a481 2026-02-17 [email protected] Fix iOS CI tests for Xcode 26 Swift compatibility (flutter/flutter#182132) 2026-02-17 [email protected] Revert "[Android] Add mechanism for setting Android engine flags via … (flutter/flutter#182388) 2026-02-17 [email protected] Roll Skia from 4ed9faf843e6 to dfe78d132e24 (1 revision) (flutter/flutter#182485) 2026-02-17 [email protected] Roll Skia from ff0af46bf172 to 4ed9faf843e6 (2 revisions) (flutter/flutter#182483) 2026-02-17 [email protected] Roll Skia from 24c7b6f5760f to ff0af46bf172 (1 revision) (flutter/flutter#182481) 2026-02-16 [email protected] Roll Dart SDK from ff57548fcf54 to 44895e617182 (1 revision) (flutter/flutter#182479) 2026-02-16 [email protected] Roll Fuchsia Linux SDK from YND8TyaxKkkkEvlD9... to mcN42vw48OPH3JDNm... (flutter/flutter#182478) 2026-02-16 [email protected] Roll Dart SDK from c819ebe0cbe3 to ff57548fcf54 (1 revision) (flutter/flutter#182472) 2026-02-16 [email protected] feat: add routes support in TestWidgetsApp (flutter/flutter#181695) 2026-02-16 [email protected] Roll Skia from 5c8a6641902f to 24c7b6f5760f (1 revision) (flutter/flutter#182467) 2026-02-16 [email protected] Roll Skia from 94d5d5e5f785 to 5c8a6641902f (6 revisions) (flutter/flutter#182463) 2026-02-16 [email protected] Roll Dart SDK from f2289e13a20a to c819ebe0cbe3 (1 revision) (flutter/flutter#182462) 2026-02-15 [email protected] Roll Dart SDK from 294e6e248512 to f2289e13a20a (1 revision) (flutter/flutter#182448) 2026-02-15 [email protected] Roll Skia from b7cea4cbe546 to 94d5d5e5f785 (1 revision) (flutter/flutter#182446) 2026-02-15 [email protected] Roll Fuchsia Linux SDK from pkyhAZ3sQZDzeNZym... to YND8TyaxKkkkEvlD9... (flutter/flutter#182445) 2026-02-15 [email protected] Roll Skia from a3a82d359a7b to b7cea4cbe546 (1 revision) (flutter/flutter#182439) 2026-02-15 [email protected] Roll Skia from a147ae2d4adc to a3a82d359a7b (1 revision) (flutter/flutter#182435) 2026-02-15 [email protected] Roll Dart SDK from f82ec89435f5 to 294e6e248512 (1 revision) (flutter/flutter#182432) 2026-02-14 [email protected] Roll Skia from 91d158b0a61e to a147ae2d4adc (2 revisions) (flutter/flutter#182424) 2026-02-14 [email protected] Roll Fuchsia Linux SDK from V30FBkJySjFKXwVjW... to pkyhAZ3sQZDzeNZym... (flutter/flutter#182423) 2026-02-14 [email protected] Roll Skia from e5a18f8f0d4a to 91d158b0a61e (1 revision) (flutter/flutter#182422) 2026-02-14 [email protected] Roll Dart SDK from 7a2a28dbd0d4 to f82ec89435f5 (2 revisions) (flutter/flutter#182414) 2026-02-14 [email protected] Roll Skia from befeec673f1b to e5a18f8f0d4a (1 revision) (flutter/flutter#182412) 2026-02-14 [email protected] Roll Skia from 7dc3ba9b1d90 to befeec673f1b (1 revision) (flutter/flutter#182408) 2026-02-14 [email protected] [Web] Fix IME and selection by syncing more text styles (flutter/flutter#180436) 2026-02-14 [email protected] Roll Skia from bb69b5b71b4f to 7dc3ba9b1d90 (2 revisions) (flutter/flutter#182401) 2026-02-14 [email protected] Disable multithread opengles, enables remaining fragment shader tests (flutter/flutter#182384) 2026-02-14 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Standardize on Test* widgets in *_tester.dart files (#182395)" (flutter/flutter#182406) 2026-02-14 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Fix cross imports for all Cupertino tests (#181634)" (flutter/flutter#182404) 2026-02-13 [email protected] Add await to tester.pump callsites (flutter/flutter#182398) 2026-02-13 [email protected] refactor: Centralize table formatting logic into a new `formatTable` utility function. (flutter/flutter#182196) 2026-02-13 [email protected] Adds impeller backend to golden workspace name (flutter/flutter#182387) 2026-02-13 [email protected] Standardize on Test* widgets in *_tester.dart files (flutter/flutter#182395) 2026-02-13 [email protected] Remove Material dependency from transformed_scrollable_test.dart (flutter/flutter#182141) 2026-02-13 [email protected] Fix cross imports for all Cupertino tests (flutter/flutter#181634) 2026-02-13 [email protected] remove MaterialApp import from raw_radio_test.dart (flutter/flutter#181721) 2026-02-13 [email protected] Roll Skia from e2991aa99710 to bb69b5b71b4f (37 revisions) (flutter/flutter#182390) 2026-02-13 [email protected] Turns on most of fragment_shader_test.dart for opengles (flutter/flutter#182229) 2026-02-13 [email protected] Update `CHANGELOG` for 3.41.1 release (flutter/flutter#182393) 2026-02-13 [email protected] Remove Material dependency from semantics_keep_alive_offstage_test.dart (flutter/flutter#182211) 2026-02-13 [email protected] Update iOS/macOS plugin template to add dependency on FlutterFramework (flutter/flutter#181416) 2026-02-13 [email protected] Add plugin dependencies to Add to App FlutterPluginRegistrant (flutter/flutter#182304) 2026-02-13 [email protected] Preparation to add contentTextStyle flag to SimpleDialog. (flutter/flutter#182200) 2026-02-13 [email protected] Roll Packages from af1d610 to 09104b0 (4 revisions) (flutter/flutter#182383) 2026-02-13 [email protected] Roll Dart SDK from de5915148fde to 7a2a28dbd0d4 (2 revisions) (flutter/flutter#182380) 2026-02-13 [email protected] [Impeller] Dispose thread local caches on each frame when using GPUSurfaceVulkanImpeller with a delegate (flutter/flutter#182265) ...
We need to do a hard breaking change in Flutter that affects `package:super_editor`: flutter#180436 To workaround the hard breaking change, we will: 1. Temporarily remove super_editor from Flutter's customer tests: flutter/tests#480 2. Roll customer tests to the flutter/flutter repo (this PR) 3. Land the breaking change in Flutter + add a breaking migration guide to the Flutter website 4. Update super_editor's [main](https://github.com/superlistapp/super_editor/commits/main) branch to handle Flutter's breaking change. 5. Re-add super_editor to customer tests, using the latest commit from super_editor's main branch. <details><summary>Why this is necessary...</summary> To fix flutter#161592, we need to pass additional styling information to the web engine. Historically, we plumb text styling information through [TextInputConnection.setStyle(...)](https://api.flutter.dev/flutter/services/TextInputConnection/setStyle.html). However, every time we add a parameter here, it creates a breaking change for super_editor's [TextInputConnectionDecorator](https://github.com/Flutter-Bounty-Hunters/super_editor/blob/068a20d3151dc4e46068b5cb54a38b0e93f7ef03/super_editor/lib/src/default_editor/document_ime/ime_decoration.dart#L8) as it implements `TextInputConnection`. To stop this cycle, we will do a one-time breaking change to move to a more extensible pattern: 1. Add new [TextInputStyle](https://github.com/flutter/flutter/pull/180436/files#diff-701a97b414026a5c87a06b546bb38ca70be2aa0ce44cbf170f4b43e8defcc91aR1530) class. This will wrap all text input styling properties. 2. Add new [TextInputConnection.updateStyle(TextInputStyle style)](https://github.com/flutter/flutter/pull/180436/files#diff-701a97b414026a5c87a06b546bb38ca70be2aa0ce44cbf170f4b43e8defcc91aR1793) method. 3. Deprecate `TextInputConnection.setStyle(...)`. This pattern lets us add more text input styling properties without breaking super_editor again. To handle this breaking change, the following code will need to be added to super_editor's [ TextInputConnectionDecorator](https://github.com/Flutter-Bounty-Hunters/super_editor/blob/068a20d3151dc4e46068b5cb54a38b0e93f7ef03/super_editor/lib/src/default_editor/document_ime/ime_decoration.dart#L8): ```dart @OverRide void updateStyle(TextInputStyle style) => client?.updateStyle(style); ``` </details> ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
fix flutter#161592 The current implementation does not fully reflect [letter-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/letter-spacing), [word-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/word-spacing), and [line-height](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-height) in the DOM. 0bc99f8 And the current implementation generates an DomHTMLTextAreaElement every time the `enabled`. Therefore, it reapplies the [scrollTop](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop) value that the Element had been holding internally. 7264912 https://github.com/user-attachments/assets/6f575366-12a0-4246-b2ab-eb2a0e85cfa5 https://github.com/user-attachments/assets/ec660d4c-5166-450c-be38-77b90fcfce76 ```dart import 'package:flutter/material.dart'; void main() => runApp(const MainApp()); class MainApp extends StatelessWidget { const MainApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp(home: MainPage()); } } class MainPage extends StatelessWidget { const MainPage({super.key}); @OverRide Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Selection position')), body: SingleChildScrollView( padding: const .all(16), child: Column( children: [ const Text('height=null'), TextField(maxLines: 5, style: TextStyle(height: null)), const Text('height=1.0'), TextField(maxLines: 5, style: TextStyle(height: 1.0)), const Text('height=2.0'), TextField(maxLines: 5, style: TextStyle(height: 2.0)), ], ), ), ); } } ``` ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
fix #161592
The current implementation does not fully reflect letter-spacing, word-spacing, and line-height in the DOM. 0bc99f8
And the current implementation generates an DomHTMLTextAreaElement every time the
enabled. Therefore, it reapplies the scrollTop value that the Element had been holding internally. 7264912after.mov
before.mov
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.