diff --git a/CHANGELOG.md b/CHANGELOG.md index a9cf707de8..94b9eaf14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * `flet build`: extensions can declare build settings, such as Android compile options and Gradle dependencies, in a `flet-extension.toml` file, applied to every app that installs them under the app's own settings. See [Build settings](/docs/extend/user-extensions#build-settings) ([#6999](https://github.com/flet-dev/flet/pull/6999)) by @ndonkoHenri. * `flet build`: add `[tool.flet.ios.entitlement]` and `--ios-entitlements` to sign iOS apps with entitlements, such as push notifications, universal links, App Groups or critical alerts. See [Entitlements](/docs/publish/ios#entitlements) ([#6999](https://github.com/flet-dev/flet/pull/6999)) by @ndonkoHenri. * `flet build`: add `[tool.flet.android]` settings for the app's [Java compile options](/docs/publish/android#java-compile-options) (`compile_options`), [Gradle dependencies](/docs/publish/android#gradle-dependencies) (`gradle_dependencies`), and manifest [receivers](/docs/publish/android#receivers) (`receiver`) and [services](/docs/publish/android#services) (`service`) ([#6999](https://github.com/flet-dev/flet/pull/6999)) by @ndonkoHenri. +* Add `text_input_action` to `TextField` and `CupertinoTextField`, with the `TextInputAction` enum, to choose the action button of the onscreen keyboard: `TextInputAction.NEXT` moves focus to the next focusable control, keeping the keyboard open when that is another text field ([#6962](https://github.com/flet-dev/flet/issues/6962), [#7021](https://github.com/flet-dev/flet/pull/7021)) by @ndonkoHenri. ### Improvements diff --git a/packages/flet/CHANGELOG.md b/packages/flet/CHANGELOG.md index 058ca5d23f..a018b2d67c 100644 --- a/packages/flet/CHANGELOG.md +++ b/packages/flet/CHANGELOG.md @@ -26,6 +26,7 @@ * **Breaking:** `parseTheme()` now builds themes from a seed with neutral surfaces by default (`#ffffff` / `#121212` and a neutral grey ramp for surface containers, `onSurface`, `outline` and inverse surfaces); the seed still drives accent colors. A theme's `surfaces: "tonal"` restores the seed-tinted Material 3 surfaces. Adds the `ThemeSurfaces` enum, `parseThemeSurfaces()` and `neutralizeSurfaces()` ([#6921](https://github.com/flet-dev/flet/issues/6921)) by @FeodorFitsner. * Import Material and Cupertino from the `material_ui` and `cupertino_ui` packages instead of `package:flutter/material.dart` and `package:flutter/cupertino.dart`. Helpers that take or return Material or Cupertino types, such as `parseColor()`, `parseTextStyle()` and `parseButtonStyle()`, now use those packages' types, and `flet.dart` no longer re-exports `Icons` and `CupertinoIcons`. Requires Flutter 3.47 or later. * Add `markdownStyleSheetFromTheme()`, which builds the default `MarkdownStyleSheet` from a `material_ui` theme. `MarkdownStyleSheet.fromTheme()` from `flutter_markdown_plus` only accepts the Flutter SDK's `ThemeData`. +* Add `text_input_action` to `TextField` and `CupertinoTextField`, with `parseTextInputAction()` and `Control.getTextInputAction()` to parse Flutter's `TextInputAction` ([#6962](https://github.com/flet-dev/flet/issues/6962)) by @ndonkoHenri. * Fix `FletBackend` running two competing reconnects when a connection reports its disconnect twice, such as a socket reset after a `flet run` hot reload ([#6913](https://github.com/flet-dev/flet/issues/6913), [#6945](https://github.com/flet-dev/flet/pull/6945)) by @ndonkoHenri. ## 1.0.4 diff --git a/packages/flet/lib/src/controls/cupertino_textfield.dart b/packages/flet/lib/src/controls/cupertino_textfield.dart index 21164cf4db..dcee6fbf31 100644 --- a/packages/flet/lib/src/controls/cupertino_textfield.dart +++ b/packages/flet/lib/src/controls/cupertino_textfield.dart @@ -285,6 +285,7 @@ class _CupertinoTextFieldControlState extends State { ? TextInputType.multiline : widget.control .getTextInputType("keyboard_type", TextInputType.text)!, + textInputAction: widget.control.getTextInputAction("text_input_action"), clearButtonSemanticLabel: widget.control.getString("clear_button_semantics_label"), autocorrect: autocorrect, diff --git a/packages/flet/lib/src/controls/textfield.dart b/packages/flet/lib/src/controls/textfield.dart index f76249262f..37ec9beee5 100644 --- a/packages/flet/lib/src/controls/textfield.dart +++ b/packages/flet/lib/src/controls/textfield.dart @@ -309,6 +309,7 @@ class _TextFieldControlState extends State { ? TextInputType.multiline : widget.control .getTextInputType("keyboard_type", TextInputType.text)!, + textInputAction: widget.control.getTextInputAction("text_input_action"), autocorrect: widget.control.getBool("autocorrect", true)!, enableSuggestions: widget.control.getBool("enable_suggestions", true)!, smartDashesType: widget.control.getBool("smart_dashes_type", true)! diff --git a/packages/flet/lib/src/utils/form_field.dart b/packages/flet/lib/src/utils/form_field.dart index f72a9eeee2..c2eb85b701 100644 --- a/packages/flet/lib/src/utils/form_field.dart +++ b/packages/flet/lib/src/utils/form_field.dart @@ -332,6 +332,11 @@ TextInputType? parseTextInputType(String? value, return typeMap[value?.toLowerCase()] ?? defaultValue; } +TextInputAction? parseTextInputAction(String? value, + [TextInputAction? defaultValue]) { + return parseEnum(TextInputAction.values, value, defaultValue); +} + InputDecoration buildInputDecoration( BuildContext context, Control control, { @@ -481,6 +486,11 @@ extension FormFieldParsers on Control { return parseTextInputType(get(propertyName), defaultValue); } + TextInputAction? getTextInputAction(String propertyName, + [TextInputAction? defaultValue]) { + return parseTextInputAction(get(propertyName), defaultValue); + } + OverlayVisibilityMode? getOverlayVisibilityMode(String propertyName, [OverlayVisibilityMode? defaultValue]) { return parseOverlayVisibilityMode(get(propertyName), defaultValue); diff --git a/sdk/python/examples/controls/material/text_field/keyboard_actions/main.py b/sdk/python/examples/controls/material/text_field/keyboard_actions/main.py new file mode 100644 index 0000000000..1fd2ed70b6 --- /dev/null +++ b/sdk/python/examples/controls/material/text_field/keyboard_actions/main.py @@ -0,0 +1,37 @@ +import flet as ft + + +def main(page: ft.Page): + def send(e: ft.Event[ft.TextField]): + status.value = f"Sent from {name.value} <{email.value}>: {message.value}" + + page.add( + ft.SafeArea( + content=ft.Column( + controls=[ + name := ft.TextField( + key="name", + label="Name", + text_input_action=ft.TextInputAction.NEXT, + ), + email := ft.TextField( + key="email", + label="Email", + keyboard_type=ft.KeyboardType.EMAIL, + text_input_action=ft.TextInputAction.NEXT, + ), + message := ft.TextField( + key="message", + label="Message", + text_input_action=ft.TextInputAction.SEND, + on_submit=send, + ), + status := ft.Text(), + ], + ), + ), + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/controls/material/text_field/keyboard_actions/pyproject.toml b/sdk/python/examples/controls/material/text_field/keyboard_actions/pyproject.toml new file mode 100644 index 0000000000..2a63b9cb62 --- /dev/null +++ b/sdk/python/examples/controls/material/text_field/keyboard_actions/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = "text-field-keyboard-actions" +version = "1.0.0" +description = "Chains form fields with the keyboard's Next button and sends the form with its Send button." +requires-python = ">=3.10" +keywords = ["text field", "keyboard", "text input action", "next", "form", "focus"] +authors = [{ name = "Flet team", email = "hello@flet.dev" }] +dependencies = ["flet"] + +[dependency-groups] +dev = ["flet-cli", "flet-desktop", "flet-web"] + +[tool.flet.gallery] +categories = ["Input/TextField"] + +[tool.flet.metadata] +title = "Keyboard actions" +docs_intro = """ +Set [`text_input_action`][flet.TextField.text_input_action] to choose the action button of the onscreen keyboard. + +Here, the keyboard's Next button moves from one field to the next while the keyboard stays open, and its Send button on the last field calls [`on_submit`][flet.TextField.on_submit]. On desktop and web, `Enter` does the same.""" +controls = ["SafeArea", "Column", "TextField", "Text"] +layout_pattern = "form" +complexity = "basic" +features = ["keyboard action button", "focus chaining", "form submission"] + +[tool.flet] +org = "dev.flet" +company = "Flet" +copyright = "Copyright (C) 2023-2026 by Flet" diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 2433a9b917..d89ede8612 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -503,6 +503,7 @@ TextCapitalization, TextField, TextFieldPasteFilesEvent, + TextInputAction, TextOnlyInputFilter, ) from flet.controls.material.time_picker import ( @@ -1205,6 +1206,7 @@ "TextField", "TextFieldPasteFilesEvent", "TextFieldTheme", + "TextInputAction", "TextOnlyInputFilter", "TextOverflow", "TextSelection", @@ -1754,6 +1756,7 @@ "TextDecorationStyle": "flet.controls.text_style", "TextField": "flet.controls.material.textfield", "TextFieldPasteFilesEvent": "flet.controls.material.textfield", + "TextInputAction": "flet.controls.material.textfield", "TextOnlyInputFilter": "flet.controls.material.textfield", "TextOverflow": "flet.controls.text_style", "TextSelection": "flet.controls.core.text", diff --git a/sdk/python/packages/flet/src/flet/controls/material/textfield.py b/sdk/python/packages/flet/src/flet/controls/material/textfield.py index b87f954dd6..4718614d05 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/textfield.py +++ b/sdk/python/packages/flet/src/flet/controls/material/textfield.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Annotated, Optional, Union @@ -28,6 +28,7 @@ "TextCapitalization", "TextField", "TextFieldPasteFilesEvent", + "TextInputAction", "TextOnlyInputFilter", ] @@ -184,7 +185,7 @@ class KeyboardType(Enum): class TextCapitalization(Enum): """ - Configures how the platform keyboard will select an uppercase or lowercase \ + Configures how the platform keyboard will select an uppercase or lowercase keyboard. Only supports text keyboards, other keyboard types will ignore this @@ -224,10 +225,143 @@ class TextCapitalization(Enum): """ +class TextInputAction(Enum): + """ + The action button of the onscreen keyboard, and what pressing it or `Enter` does. + + Each action lists the mobile platforms whose keyboards support it. In debug + builds on Android and iOS, such as an app running on the iOS Simulator, a field + with an action that the platform doesn't support gets focus without showing the + keyboard, and no error is reported. On Android, release builds show the + keyboard with its default action button instead. + """ + + NONE = "none" + """ + No action specific to the field; the platform decides how the keyboard looks. + + Keeps focus in the field, so the keyboard stays open. + + Note: + Supported on Android only. + """ + + UNSPECIFIED = "unspecified" + """ + Lets the platform choose the most appropriate action. + + Keeps focus in the field, so the keyboard stays open. + + Supported on Android and iOS. + """ + + DONE = "done" + """ + The user is done entering input, such as at the end of a form. + + Removes focus from the field, which closes the keyboard. + + Supported on Android and iOS. + """ + + GO = "go" + """ + Takes the user to the destination they typed, such as a URL. + + Removes focus from the field, which closes the keyboard. + + Supported on Android and iOS. + """ + + SEARCH = "search" + """ + Runs a search for the typed text. + + Removes focus from the field, which closes the keyboard. + + Supported on Android and iOS. + """ + + SEND = "send" + """ + Sends what the user typed, such as a message. + + Removes focus from the field, which closes the keyboard. + + Supported on Android and iOS. + """ + + NEXT = "next" + """ + Moves focus to the next focusable control, keeping the keyboard open when it's + another text field. + + Supported on Android and iOS. + """ + + PREVIOUS = "previous" + """ + Moves focus to the previous focusable control, keeping the keyboard open when + it's another text field. + + Note: + Supported on Android only. + """ + + CONTINUE_ACTION = "continueAction" + """ + Continues to the next step, such as the next page of a sign-up flow. + + Doesn't move focus: the field keeps it, so the keyboard stays open. + + Note: + Supported on iOS only. + """ + + JOIN = "join" + """ + Joins something, such as a wireless network. + + Keeps focus in the field, so the keyboard stays open. + + Note: + Supported on iOS only. + """ + + ROUTE = "route" + """ + Shows routing options, such as driving directions. + + Keeps focus in the field, so the keyboard stays open. + + Note: + Supported on iOS only. + """ + + EMERGENCY_CALL = "emergencyCall" + """ + Calls emergency services. + + Keeps focus in the field, so the keyboard stays open. + + Note: + Supported on iOS only. + """ + + NEWLINE = "newline" + """ + Inserts a new line into a multiline field. + + On a single-line field, it removes focus from the field like :attr:`DONE`. + + Supported on Android and iOS. + """ + + @value class InputFilter: """ - An input filter that uses a regular expression to allow or deny/block certain \ + An input filter that uses a regular expression to allow or deny/block certain patterns in the input. """ @@ -308,7 +442,7 @@ def __init__(self): ) class TextField(FormFieldControl, AdaptiveControl): """ - A text field lets the user enter text, either with hardware keyboard or with an \ + A text field lets the user enter text, either with hardware keyboard or with an onscreen keyboard. Example: @@ -407,7 +541,7 @@ class TextField(FormFieldControl, AdaptiveControl): can_reveal_password: bool = False """ - Displays a toggle icon button that allows revealing the entered password. Is shown \ + Displays a toggle icon button that allows revealing the entered password. Is shown if both :attr:`password` and `can_reveal_password` are `True`. The icon is displayed in the same location as :attr:`flet.FormFieldControl.suffix` @@ -425,8 +559,8 @@ class TextField(FormFieldControl, AdaptiveControl): shift_enter: bool = False """ - Changes the behavior of `Enter` button in :attr:`multiline` textfield to be \ - chat-like, i.e. new line can be added with `Shift`+`Enter` and pressing just \ + Changes the behavior of `Enter` button in :attr:`multiline` textfield to be + chat-like, i.e. new line can be added with `Shift`+`Enter` and pressing just `Enter` fires :attr:`on_submit` event. """ @@ -704,6 +838,38 @@ class TextField(FormFieldControl, AdaptiveControl): details. """ + text_input_action: Optional[TextInputAction] = field(default=None, kw_only=True) + """ + The action button of the onscreen keyboard, and what pressing it or `Enter` does. + + :attr:`~flet.TextInputAction.NEXT` and :attr:`~flet.TextInputAction.PREVIOUS` + move focus to the next or previous focusable control in reading order, which + can be a button in this field's :attr:`~flet.FormFieldControl.suffix`. When + that control is another text field, the keyboard stays open. + See :class:`~flet.TextInputAction` for what the other actions do and the + platforms that support them. + + On a single-line field, every action also calls :attr:`on_submit`. + On a :attr:`multiline` field, the action doesn't call :attr:`on_submit`, and + with an action other than :attr:`~flet.TextInputAction.NEWLINE`, `Enter` and + the keyboard's action key usually perform the action instead of inserting a + new line. On the web, that includes `Shift+Enter` on a :attr:`shift_enter` + field. + + If not set, a single-line field uses :attr:`~flet.TextInputAction.DONE` and a + :attr:`multiline` field uses :attr:`~flet.TextInputAction.NEWLINE`. + + A change made while this field has focus applies the next time it gets focus. + + Example: + ```python + page.add( + ft.TextField(label="Name", text_input_action=ft.TextInputAction.NEXT), + ft.TextField(label="Email", text_input_action=ft.TextInputAction.DONE), + ) + ``` + """ + def _migrate_state(self, other: BaseControl): super()._migrate_state(other) if ( diff --git a/website/docs/controls/textfield.md b/website/docs/controls/textfield.md index 53716880cd..a77813b48a 100644 --- a/website/docs/controls/textfield.md +++ b/website/docs/controls/textfield.md @@ -32,6 +32,8 @@ import {ClassMembers, ClassSummary, CodeExample, Image} from '@site/src/componen multiline + + underlined-and-borderless diff --git a/website/docs/types/textinputaction.md b/website/docs/types/textinputaction.md new file mode 100644 index 0000000000..953587b2fa --- /dev/null +++ b/website/docs/types/textinputaction.md @@ -0,0 +1,15 @@ +--- +class_name: "flet.TextInputAction" +examples: "controls/material/text_field" +title: "TextInputAction" +--- + +import {ClassMembers, ClassSummary, CodeExample} from '@site/src/components/crocodocs'; + + + +## Examples + + + + diff --git a/website/sidebars.yml b/website/sidebars.yml index 8a4d5624df..966411877c 100644 --- a/website/sidebars.yml +++ b/website/sidebars.yml @@ -963,6 +963,7 @@ docs: - types/textbaseline.md - types/textcapitalization.md - types/textdecorationstyle.md + - types/textinputaction.md - types/textoverflow.md - types/textselectionchangecause.md - types/thememode.md