Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export 'engine/text/word_break_properties.dart';
export 'engine/text/word_breaker.dart';
export 'engine/text_editing/autofill_hint.dart';
export 'engine/text_editing/composition_aware_mixin.dart';
export 'engine/text_editing/input_action.dart';
export 'engine/text_editing/input_type.dart';
export 'engine/text_editing/text_capitalization.dart';
export 'engine/text_editing/text_editing.dart';
Expand Down
155 changes: 155 additions & 0 deletions lib/web_ui/lib/src/engine/text_editing/input_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2022 The Flutter Authors. All rights reserved.
Comment thread
TimWhiting marked this conversation as resolved.
Outdated
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../browser_detection.dart';
import '../dom.dart';

/// Various input action types used in text fields.
///
/// These types are coming from Flutter's [TextInputAction]. Currently, the web doesn't
/// support all the types. We fallback to [EngineInputAction.none] when Flutter
/// sends a type that isn't supported.
abstract class EngineInputAction {
const EngineInputAction();

static EngineInputAction fromName(String name) {
switch (name) {
case 'TextInputAction.continueAction':
case 'TextInputAction.next':
return next;
case 'TextInputAction.previous':
return previous;
case 'TextInputAction.done':
return done;
case 'TextInputAction.go':
return go;
case 'TextInputAction.newline':
return enter;
case 'TextInputAction.search':
return search;
case 'TextInputAction.send':
return send;
Comment thread
TimWhiting marked this conversation as resolved.
Outdated
case 'TextInputAction.emergencyCall':
case 'TextInputAction.join':
case 'TextInputAction.none':
case 'TextInputAction.route':
case 'TextInputAction.unspecified':
default:
return none;
}
}

/// No input action
static const NoInputAction none = NoInputAction();

/// Action to go to next
static const NextInputAction next = NextInputAction();

/// Action to go to previous
static const PreviousInputAction previous = PreviousInputAction();

/// Action to be finished
static const DoneInputAction done = DoneInputAction();

/// Action to Go
static const GoInputAction go = GoInputAction();

/// Action to insert newline
static const EnterInputAction enter = EnterInputAction();

/// Action to search
static const SearchInputAction search = SearchInputAction();

/// Action to send
static const SendInputAction send = SendInputAction();


/// The HTML `enterkeyhint` attribute to be set on the DOM element.
///
/// This HTML attribute helps the browser decide what kind of keyboard action
/// to use for this text field
///
/// For various `enterkeyhint` values supported by browsers, see:
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint>.
String? get enterkeyhintAttribute;

/// Given a [domElement], set attributes that are specific to this input action.
void configureInputAction(DomHTMLElement domElement) {
if (enterkeyhintAttribute == null) {
return;
}

// Only apply `enterkeyhint` in mobile browsers so that the right virtual
// keyboard shows up.
Comment thread
TimWhiting marked this conversation as resolved.
if (operatingSystem == OperatingSystem.iOs ||
operatingSystem == OperatingSystem.android ||
enterkeyhintAttribute == EngineInputAction.none.enterkeyhintAttribute) {
domElement.setAttribute('enterkeyhint', enterkeyhintAttribute!);
}
}
}

/// No action specified
class NoInputAction extends EngineInputAction {
const NoInputAction();

@override
String? get enterkeyhintAttribute => null;
}

/// Typically inserting a new line.
class EnterInputAction extends EngineInputAction {
const EnterInputAction();

@override
String? get enterkeyhintAttribute => 'enter';
}

/// Typically meaning there is nothing more to input and the input method editor (IME) will be closed.
class DoneInputAction extends EngineInputAction {
const DoneInputAction();

@override
String? get enterkeyhintAttribute => 'done';
}

/// Typically meaning to take the user to the target of the text they typed.
class GoInputAction extends EngineInputAction {
const GoInputAction();

@override
String? get enterkeyhintAttribute => 'go';
}

/// Typically taking the user to the next field that will accept text.
class NextInputAction extends EngineInputAction {
const NextInputAction();

@override
String? get enterkeyhintAttribute => 'next';
}

/// Typically taking the user to the previous field that will accept text.
class PreviousInputAction extends EngineInputAction {
const PreviousInputAction();

@override
String? get enterkeyhintAttribute => 'previous';
}

/// Typically taking the user to the results of searching for the text they have typed.
class SearchInputAction extends EngineInputAction {
const SearchInputAction();

@override
String? get enterkeyhintAttribute => 'search';
}

/// Typically delivering the text to its target.
class SendInputAction extends EngineInputAction {
const SendInputAction();

@override
String? get enterkeyhintAttribute => 'send';
}
4 changes: 4 additions & 0 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../text/paragraph.dart';
import '../util.dart';
import 'autofill_hint.dart';
import 'composition_aware_mixin.dart';
import 'input_action.dart';
import 'input_type.dart';
import 'text_capitalization.dart';

Expand Down Expand Up @@ -1181,6 +1182,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
activeDomElement.setAttribute('inputmode', 'none');
}

final EngineInputAction action = EngineInputAction.fromName(config.inputAction);
action.configureInputAction(activeDomElement);

final AutofillInfo? autofill = config.autofill;
if (autofill != null) {
autofill.applyToDomElement(activeDomElement, focusedElement: true);
Expand Down
21 changes: 21 additions & 0 deletions lib/web_ui/test/text_editing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,27 @@ Future<void> testMain() async {
editingStrategy!.disable();
});

test('Knows how to create non-default text actions', () {
final InputConfiguration config = InputConfiguration(
inputAction: 'TextInputAction.send'
);
editingStrategy!.enable(
config,
onChange: trackEditingState,
onAction: trackInputAction,
);
expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1));
final DomElement input = defaultTextEditingRoot.querySelector('input')!;
expect(editingStrategy!.domElement, input);
if (operatingSystem == OperatingSystem.iOs || operatingSystem == OperatingSystem.android){
expect(input.getAttribute('enterkeyhint'), 'send');
} else {
expect(input.getAttribute('enterkeyhint'), null);
}

editingStrategy!.disable();
});

test('Knows to turn autocorrect off', () {
final InputConfiguration config = InputConfiguration(
autocorrect: false,
Expand Down