diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index af715b32ed..ab09b6c674 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -666,13 +666,13 @@ class EditingState { this.text, int? baseOffset, int? extentOffset, - this.composingBaseOffset, - this.composingExtentOffset + this.composingBaseOffset = -1, + this.composingExtentOffset = -1 }) : - // Don't allow negative numbers. Pick the smallest selection index for base. - baseOffset = math.max(0, math.min(baseOffset ?? 0, extentOffset ?? 0)), - // Don't allow negative numbers. Pick the greatest selection index for extent. - extentOffset = math.max(0, math.max(baseOffset ?? 0, extentOffset ?? 0)); + // Don't allow negative numbers. + baseOffset = math.max(0, baseOffset ?? 0), + // Don't allow negative numbers. + extentOffset = math.max(0, extentOffset ?? 0); /// Creates an [EditingState] instance using values from an editing state Map /// coming from Flutter. @@ -707,8 +707,8 @@ class EditingState { text: text, baseOffset: selectionBase, extentOffset: selectionExtent, - composingBaseOffset: composingBase, - composingExtentOffset: composingExtent + composingBaseOffset: composingBase ?? -1, + composingExtentOffset: composingExtent ?? -1 ); } @@ -736,6 +736,11 @@ class EditingState { } } + // Pick the smallest selection index for base. + int get minOffset => math.min(baseOffset ?? 0, extentOffset ?? 0); + // Pick the greatest selection index for extent. + int get maxOffset => math.max(baseOffset ?? 0, extentOffset ?? 0); + EditingState copyWith({ String? text, int? baseOffset, @@ -773,10 +778,10 @@ class EditingState { final int? extentOffset; /// The offset at which [CompositionAwareMixin.composingText] begins, if any. - final int? composingBaseOffset; + final int composingBaseOffset; /// The offset at which [CompositionAwareMixin.composingText] terminates, if any. - final int? composingExtentOffset; + final int composingExtentOffset; /// Whether the current editing state is valid or not. bool get isValid => baseOffset! >= 0 && extentOffset! >= 0; @@ -796,8 +801,8 @@ class EditingState { } return other is EditingState && other.text == text && - other.baseOffset == baseOffset && - other.extentOffset == extentOffset && + other.minOffset == minOffset && + other.maxOffset == maxOffset && other.composingBaseOffset == composingBaseOffset && other.composingExtentOffset == composingExtentOffset; } @@ -825,12 +830,12 @@ class EditingState { if (domInstanceOfString(domElement, 'HTMLInputElement')) { final DomHTMLInputElement element = domElement! as DomHTMLInputElement; element.value = text; - element.setSelectionRange(baseOffset!, extentOffset!); + element.setSelectionRange(minOffset, maxOffset); } else if (domInstanceOfString(domElement, 'HTMLTextAreaElement')) { final DomHTMLTextAreaElement element = domElement! as DomHTMLTextAreaElement; element.value = text; - element.setSelectionRange(baseOffset!, extentOffset!); + element.setSelectionRange(minOffset, maxOffset); } else { throw UnsupportedError('Unsupported DOM element type: <${domElement?.tagName}> (${domElement.runtimeType})'); } diff --git a/engine/src/flutter/lib/web_ui/test/composition_test.dart b/engine/src/flutter/lib/web_ui/test/composition_test.dart index a2d5919141..5d4b9fb6a8 100644 --- a/engine/src/flutter/lib/web_ui/test/composition_test.dart +++ b/engine/src/flutter/lib/web_ui/test/composition_test.dart @@ -184,7 +184,7 @@ Future testMain() async { expect( editingStrategy.lastEditingState, isA() - .having((EditingState editingState) => editingState.composingBaseOffset!, + .having((EditingState editingState) => editingState.composingBaseOffset, 'composingBaseOffset', beforeComposingText.length - composingText.length) .having((EditingState editingState) => editingState.composingExtentOffset, 'composingExtentOffset', beforeComposingText.length)); diff --git a/engine/src/flutter/lib/web_ui/test/text_editing_test.dart b/engine/src/flutter/lib/web_ui/test/text_editing_test.dart index 7679c715c4..dfee917cc9 100644 --- a/engine/src/flutter/lib/web_ui/test/text_editing_test.dart +++ b/engine/src/flutter/lib/web_ui/test/text_editing_test.dart @@ -1557,8 +1557,8 @@ Future testMain() async { 'text': 'something', 'selectionBase': 9, 'selectionExtent': 9, - 'composingBase': null, - 'composingExtent': null + 'composingBase': -1, + 'composingExtent': -1 } ], ); @@ -1583,8 +1583,8 @@ Future testMain() async { 'text': 'something', 'selectionBase': 2, 'selectionExtent': 5, - 'composingBase': null, - 'composingExtent': null + 'composingBase': -1, + 'composingExtent': -1 } ], ); @@ -1641,8 +1641,8 @@ Future testMain() async { 'deltaEnd': -1, 'selectionBase': 2, 'selectionExtent': 5, - 'composingBase': null, - 'composingExtent': null + 'composingBase': -1, + 'composingExtent': -1 } ], } @@ -1724,8 +1724,8 @@ Future testMain() async { 'text': 'something', 'selectionBase': 9, 'selectionExtent': 9, - 'composingBase': null, - 'composingExtent': null + 'composingBase': -1, + 'composingExtent': -1 } }, ], @@ -1796,8 +1796,8 @@ Future testMain() async { 'text': 'something\nelse', 'selectionBase': 14, 'selectionExtent': 14, - 'composingBase': null, - 'composingExtent': null + 'composingBase': -1, + 'composingExtent': -1 } ], ); @@ -1812,8 +1812,8 @@ Future testMain() async { 'text': 'something\nelse', 'selectionBase': 2, 'selectionExtent': 5, - 'composingBase': null, - 'composingExtent': null + 'composingBase': -1, + 'composingExtent': -1 } ], ); @@ -2231,6 +2231,34 @@ Future testMain() async { ); }); + test('Sets default composing offsets if none given', () { + final EditingState editingState = + EditingState(text: 'Test', baseOffset: 2, extentOffset: 4); + final EditingState editingStateFromFrameworkMsg = + EditingState.fromFrameworkMessage({ + 'selectionBase': 10, + 'selectionExtent': 4, + }); + + expect(editingState.composingBaseOffset, -1); + expect(editingState.composingExtentOffset, -1); + + expect(editingStateFromFrameworkMsg.composingBaseOffset, -1); + expect(editingStateFromFrameworkMsg.composingExtentOffset, -1); + }); + + test('Correctly identifies min and max offsets', () { + final EditingState flippedEditingState = + EditingState(baseOffset: 10, extentOffset: 4); + final EditingState normalEditingState = + EditingState(baseOffset: 2, extentOffset: 6); + + expect(flippedEditingState.minOffset, 4); + expect(flippedEditingState.maxOffset, 10); + expect(normalEditingState.minOffset, 2); + expect(normalEditingState.maxOffset, 6); + }); + test('Configure input element from the editing state', () { final DomHTMLInputElement input = defaultTextEditingRoot.querySelector('input')! as DomHTMLInputElement; @@ -2264,6 +2292,20 @@ Future testMain() async { expect(textArea.selectionEnd, 2); }); + test('Configure input element editing state for a flipped base and extent', + () { + final DomHTMLInputElement input = + defaultTextEditingRoot.querySelector('input')! as DomHTMLInputElement; + editingState = + EditingState(text: 'Hello World', baseOffset: 10, extentOffset: 2); + + editingState.applyToDomElement(input); + + expect(input.value, 'Hello World'); + expect(input.selectionStart, 2); + expect(input.selectionEnd, 10); + }); + test('Get Editing State from input element', () { final DomHTMLInputElement input = defaultTextEditingRoot.querySelector('input')! as DomHTMLInputElement; @@ -2318,6 +2360,17 @@ Future testMain() async { expect(editingState1 != editingState3, isTrue); }); + test('Takes flipped base and extent offsets into account', () { + final EditingState flippedEditingState = + EditingState(baseOffset: 10, extentOffset: 4); + final EditingState normalEditingState = + EditingState(baseOffset: 4, extentOffset: 10); + + expect(normalEditingState, flippedEditingState); + + expect(normalEditingState == flippedEditingState, isTrue); + }); + test('takes composition range into account', () { final EditingState editingState1 = EditingState(composingBaseOffset: 1, composingExtentOffset: 2); final EditingState editingState2 = EditingState(composingBaseOffset: 1, composingExtentOffset: 2);