From 109c480967f8ac41b82d2f89d8fd7b38545295f5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Jun 2024 16:26:08 +0800 Subject: [PATCH] First working textfield implementation --- forui/CHANGELOG.md | 4 + .../lib/src/widgets/form/text_form_field.dart | 425 +++++++++++++++- .../src/widgets/text_field/text_field.dart | 417 +++------------- .../widgets/text_field/text_field_mixin.dart | 466 ++++++++++++++++++ forui/lib/theme.dart | 4 +- forui/lib/widgets.dart | 3 +- 6 files changed, 957 insertions(+), 362 deletions(-) create mode 100644 forui/lib/src/widgets/text_field/text_field_mixin.dart diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md index 3eb1ac62d..f21521267 100644 --- a/forui/CHANGELOG.md +++ b/forui/CHANGELOG.md @@ -1,8 +1,12 @@ ## Next +### Changes * Change default `FTextFieldStyle`'s vertical content padding from `5` to `15`. * Split exports in `forui.dart` into sub-libraries. +## Fixes +* Fix missing `key` parameter in `FTextField` constructors. + ## 0.1.0 * Initial release! 🚀 diff --git a/forui/lib/src/widgets/form/text_form_field.dart b/forui/lib/src/widgets/form/text_form_field.dart index ec200d318..9b2067be2 100644 --- a/forui/lib/src/widgets/form/text_form_field.dart +++ b/forui/lib/src/widgets/form/text_form_field.dart @@ -1,19 +1,434 @@ - +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:forui/src/widgets/text_field/text_field.dart'; + +/// A [FormField] that contains a [FTextField]. +/// +/// This is a convenience widget that wraps a [FTextField] widget in a [FormField]. +/// +/// See: +/// * https://forui.dev/docs/text-field for working text-field examples. +/// * [FTextFieldStyle] for customizing a text field's appearance. +/// * [FTextField], which is the underlying text field without the Form integration. +/// * [TextFormField] for more details about working with a text form field. +class FTextFormField extends FormField with FTextFieldMixin { + + static Widget _textField(FormFieldState field) { + final state = field as _State; + final widget = state.widget; + + return UnmanagedRestorationScope( + bucket: state.bucket, + child: FTextField( + style: widget.style, + label: widget.label, + rawLabel: widget.rawLabel, + hint: widget.hint, + hintMaxLines: widget.hintMaxLines, + help: widget.help, + rawHelp: widget.rawHelp, + helpMaxLines: widget.helpMaxLines, + error: widget.error, + rawError: widget.rawError, + errorMaxLines: widget.errorMaxLines, + magnifierConfiguration: widget.magnifierConfiguration, + controller: state._effectiveController, + focusNode: widget.focusNode, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + textDirection: widget.textDirection, + autofocus: widget.autofocus, + statesController: widget.statesController, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + minLines: widget.minLines, + maxLines: widget.maxLines, + expands: widget.expands, + readOnly: widget.readOnly, + showCursor: widget.showCursor, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + onChange: (value) { + field.didChange(value); + widget.onChange?.call(value); + }, + onEditingComplete: widget.onEditingComplete, + onSubmit: widget.onSubmit, + onAppPrivateCommand: widget.onAppPrivateCommand, + inputFormatters: widget.inputFormatters, + enabled: widget.enabled, + ignorePointers: widget.ignorePointers, + enableInteractSelection: widget.enableInteractSelection, + selectionControls: widget.selectionControls, + dragStartBehavior: widget.dragStartBehavior, + scrollPhysics: widget.scrollPhysics, + scrollController: widget.scrollController, + autofillHints: widget.autofillHints, + restorationId: widget.restorationId, + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contextMenuBuilder: widget.contextMenuBuilder, + canRequestFocus: widget.canRequestFocus, + undoController: widget.undoController, + spellCheckConfiguration: widget.spellCheckConfiguration, + suffixIcon: widget.suffixIcon, + key: widget.key, + ), + ); + } + + @override + final FTextFieldStyle? style; + + @override + final String? label; + + @override + final Widget? rawLabel; + + @override + final String? hint; + + @override + final int? hintMaxLines; + + @override + final String? help; + + @override + final Widget? rawHelp; + + @override + final int? helpMaxLines; + + @override + final String? error; + + @override + final Widget? rawError; + + @override + final int? errorMaxLines; + + @override + final TextMagnifierConfiguration? magnifierConfiguration; + + @override + final TextEditingController? controller; + + @override + final FocusNode? focusNode; + + @override + final TextInputType? keyboardType; -class FTextFormField extends FormField { + @override + final TextInputAction? textInputAction; + + @override + final TextCapitalization textCapitalization; + + @override + final TextAlign textAlign; + + @override + final TextAlignVertical? textAlignVertical; + + @override + final TextDirection? textDirection; + + @override + final bool autofocus; + + @override + final WidgetStatesController? statesController; + + @override + final bool obscureText; + + @override + final bool autocorrect; + @override + final SmartDashesType? smartDashesType; + + @override + final SmartQuotesType? smartQuotesType; + + @override + final bool enableSuggestions; + + @override + final int? minLines; + + @override + final int? maxLines; + + @override + final bool expands; + + @override + final bool readOnly; + + @override + final bool? showCursor; + + @override + final int? maxLength; + + @override + final MaxLengthEnforcement? maxLengthEnforcement; + + @override + final ValueChanged? onChange; + + @override + final VoidCallback? onEditingComplete; + + @override + final ValueChanged? onSubmit; + + @override + final AppPrivateCommandCallback? onAppPrivateCommand; + + @override + final List? inputFormatters; + + @override + final bool? ignorePointers; + + @override + final bool enableInteractSelection; + + @override + final TextSelectionControls? selectionControls; + + @override + final DragStartBehavior dragStartBehavior; + + @override + final ScrollPhysics? scrollPhysics; + + @override + final ScrollController? scrollController; + + @override + final Iterable? autofillHints; + + @override + final bool scribbleEnabled; + + @override + final bool enableIMEPersonalizedLearning; + + @override + final EditableTextContextMenuBuilder? contextMenuBuilder; + + @override + final bool canRequestFocus; + + @override + final UndoHistoryController? undoController; + + @override + final SpellCheckConfiguration? spellCheckConfiguration; + + @override + final Widget? suffixIcon; + + /// Creates a [FTextFormField]. + /// + /// ## Contract: + /// Throws [AssertionError] if: + /// * both [label] and [rawLabel] are not null + /// * both [help] and [rawHelp] are not null + /// * both [error] and [rawError] are not null FTextFormField({ + this.style, + this.label, + this.rawLabel, + this.hint, + this.hintMaxLines, + this.help, + this.rawHelp, + this.helpMaxLines, + this.error, + this.rawError, + this.errorMaxLines, + this.magnifierConfiguration, + this.controller, + this.focusNode, + this.keyboardType, + this.textInputAction, + this.textCapitalization = TextCapitalization.none, + this.textAlign = TextAlign.start, + this.textAlignVertical, + this.textDirection, + this.autofocus = false, + this.statesController, + this.obscureText = false, + this.autocorrect = true, + this.smartDashesType, + this.smartQuotesType, + this.enableSuggestions = true, + this.minLines, + this.maxLines, + this.expands = false, + this.readOnly = false, + this.showCursor, + this.maxLength, + this.maxLengthEnforcement, + this.onChange, + this.onEditingComplete, + this.onSubmit, + this.onAppPrivateCommand, + this.inputFormatters, + this.ignorePointers, + this.enableInteractSelection = true, + this.selectionControls, + this.dragStartBehavior = DragStartBehavior.start, + this.scrollPhysics, + this.scrollController, + this.autofillHints, + this.scribbleEnabled = true, + this.enableIMEPersonalizedLearning = true, + this.contextMenuBuilder = defaultContextMenuBuilder, + this.canRequestFocus = true, + this.undoController, + this.spellCheckConfiguration, + this.suffixIcon, + String? initialValue, + super.key, + super.enabled = true, super.autovalidateMode, - }): super( - builder: (state) - ); + super.restorationId, + super.onSaved, + super.validator, + }): + assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'), + assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'), + assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.'), + assert(initialValue == null || controller == null, 'Cannot provide both a initialValue and a controller.'), + super( + initialValue: controller != null ? controller.text : (initialValue ?? ''), + builder: _textField, + ); @override FormFieldState createState() => _State(); } +// This class is based on Material's _TextFormFieldState implementation. class _State extends FormFieldState { + RestorableTextEditingController? _controller; + + @override + void initState() { + super.initState(); + + final controller = widget.controller; + if (controller == null) { + _createLocalController(widget.initialValue != null ? TextEditingValue(text: widget.initialValue!) : null); + + } else { + controller.addListener(_handleControllerChanged); + } + } + + @override + void restoreState(RestorationBucket? oldBucket, bool initialRestore) { + super.restoreState(oldBucket, initialRestore); + if (_controller != null) { + registerForRestoration(_controller!, 'controller'); + } + + // Make sure to update the internal [FormFieldState] value to sync up with + // text editing controller value. + setValue(_effectiveController.text); + } + + void _createLocalController([TextEditingValue? value]) { + _controller = value == null ? RestorableTextEditingController() : RestorableTextEditingController.fromValue(value); + if (!restorePending) { + registerForRestoration(_controller!, 'controller'); + } + } + + @override + void didUpdateWidget(TextFormField old) { + super.didUpdateWidget(old); + if (widget.controller == old.controller) { + return; + } + + old.controller?.removeListener(_handleControllerChanged); + widget.controller?.addListener(_handleControllerChanged); + + if (old.controller != null && widget.controller == null) { + _createLocalController(old.controller!.value); + } + + if (widget.controller != null) { + setValue(widget.controller!.text); + if (old.controller == null) { + unregisterFromRestoration(_controller!); + _controller!.dispose(); + _controller = null; + } + } + } + + @override + void dispose() { + widget.controller?.removeListener(_handleControllerChanged); + _controller?.dispose(); + super.dispose(); + } + + @override + void didChange(String? value) { + super.didChange(value); + + if (_effectiveController.text != value) { + _effectiveController.text = value ?? ''; + } + } + + @override + void reset() { + // Set the controller value before calling super.reset() to let + // _handleControllerChanged suppress the change. + _effectiveController.text = widget.initialValue ?? ''; + super.reset(); + widget.onChange?.call(_effectiveController.text); + } + + + void _handleControllerChanged() { + // Suppress changes that originated from within this class. + // + // In the case where a controller has been passed in to this widget, we + // register this change listener. In these cases, we'll also receive change + // notifications for changes originating from within this class -- for + // example, the reset() method. In such cases, the FormField value will + // already have been set. + if (_effectiveController.text != value) { + didChange(_effectiveController.text); + } + } + + @override + FTextFormField get widget => super.widget as FTextFormField; + + TextEditingController get _effectiveController => widget.controller ?? _controller!.value; + } diff --git a/forui/lib/src/widgets/text_field/text_field.dart b/forui/lib/src/widgets/text_field/text_field.dart index 045430098..aca15993a 100644 --- a/forui/lib/src/widgets/text_field/text_field.dart +++ b/forui/lib/src/widgets/text_field/text_field.dart @@ -9,16 +9,9 @@ import 'package:meta/meta.dart'; import 'package:forui/forui.dart'; +part 'text_field_mixin.dart'; part 'text_field_style.dart'; -/// The default context menu builder. -@internal -Widget defaultContextMenuBuilder( - BuildContext context, - EditableTextState state, -) => AdaptiveTextSelectionToolbar.editableText(editableTextState: state); - - /// A text field. /// /// It lets the user enter text, either with hardware keyboard or with an onscreen keyboard. @@ -26,459 +19,172 @@ Widget defaultContextMenuBuilder( /// See: /// * https://forui.dev/docs/text-field for working examples. /// * [FTextFieldStyle] for customizing a text field's appearance. +/// * [FTextFormField] for a text field that integrates with a [Form]. /// * [TextField] for more details about working with a text field. -final class FTextField extends StatelessWidget { - /// The text field's style. Defaults to [FThemeData.textFieldStyle]. +final class FTextField extends StatelessWidget with FTextFieldMixin { + @override final FTextFieldStyle? style; - /// The label above a text field. - /// - /// ## Contract: - /// Throws [AssertionError] if: - /// * both [label] and [rawLabel] are not null + @override final String? label; - /// The raw label above a text field. - /// - /// ## Contract: - /// Throws [AssertionError] if: - /// * both [label] and [rawLabel] are not null + @override final Widget? rawLabel; - /// The text to display when the text field is empty. - /// - /// See [InputDecoration.hintText] for more information. + @override final String? hint; - /// The maximum number of lines the [hint] can occupy. Defaults to the value of [TextField.maxLines] attribute. - /// - /// See [InputDecoration.hintMaxLines] for more information. + @override final int? hintMaxLines; - /// The help text. - /// - /// See [InputDecoration.helperText] for more information. + @override final String? help; - /// The raw help text. + @override final Widget? rawHelp; - /// The maximum number of lines the [help] can occupy. Defaults to the value of [TextField.maxLines] attribute. - /// - /// See [InputDecoration.helperMaxLines] for more information. + @override final int? helpMaxLines; - /// The error text. - /// - /// See [InputDecoration.errorText] for more information. + @override final String? error; - /// The raw error text. + @override final Widget? rawError; - /// The maximum number of lines the [error] can occupy. Defaults to the value of [TextField.maxLines] attribute. - /// - /// See [InputDecoration.errorMaxLines] for more information. + @override final int? errorMaxLines; - /// The configuration for the magnifier of this text field. - /// - /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] on Android, and builds nothing on all - /// other platforms. To suppress the magnifier, consider passing [TextMagnifierConfiguration.disabled]. + @override final TextMagnifierConfiguration? magnifierConfiguration; - /// Controls the text being edited. If null, this widget will create its own [TextEditingController]. + @override final TextEditingController? controller; - /// Defines the keyboard focus for this - /// - /// See [TextField.focusNode] for more information. + @override final FocusNode? focusNode; - /// The type of keyboard to use for editing the text. Defaults to [TextInputType.text] if maxLines is one and - /// [TextInputType.multiline] otherwise. + @override final TextInputType? keyboardType; - /// The type of action button to use for the keyboard. - /// - /// Defaults to [TextInputAction.newline] if [keyboardType] is [TextInputType.multiline] and [TextInputAction.done] - /// otherwise. + @override final TextInputAction? textInputAction; - /// Configures how the platform keyboard will select an uppercase or lowercase keyboard. Defaults to - /// [TextCapitalization.none]. - /// - /// Only supports text keyboards, other keyboard types will ignore this configuration. Capitalization is locale-aware. - /// - /// See [TextCapitalization] for a description of each capitalization behavior. + @override final TextCapitalization textCapitalization; - /// How the text should be aligned horizontally. - /// - /// Defaults to [TextAlign.start]. + @override final TextAlign textAlign; - /// How the text should be aligned vertically. - /// - /// See [TextAlignVertical] for more information. + @override final TextAlignVertical? textAlignVertical; - /// The directionality of the text. Defaults to the ambient [Directionality], if any. - /// - /// See [TextField.textDirection] for more information. + @override final TextDirection? textDirection; - /// Whether this text field should focus itself if nothing else is already focused. Defaults to false. - /// - /// If true, the keyboard will open as soon as this text field obtains focus. Otherwise, the keyboard is only shown - /// after the user taps the text field. + @override final bool autofocus; - /// Represents the interactive "state" of this widget in terms of a set of [WidgetState]s, including - /// [WidgetState.disabled], [WidgetState.hovered], [WidgetState.error], and [WidgetState.focused]. - /// - /// See [TextField.statesController] for more information. + @override final WidgetStatesController? statesController; - /// Whether to hide the text being edited (e.g., for passwords). Defaults to false. - /// - /// When this is set to true, all the characters in the text field are obscured, and the text in the field cannot be - /// copied with copy or cut. If [readOnly] is also true, then the text cannot be selected. + @override final bool obscureText; - /// Whether to enable autocorrection. Defaults to true. + @override final bool autocorrect; - /// Whether to allow the platform to automatically format dashes. - /// - /// See [TextField.smartDashesType] for more information. + @override final SmartDashesType? smartDashesType; - /// Whether to allow the platform to automatically format quotes. - /// - /// See [TextField.smartQuotesType] for more information. + @override final SmartQuotesType? smartQuotesType; - /// Whether to show input suggestions as the user types. Defaults to true. - /// - /// This flag only affects Android. On iOS, suggestions are tied directly to [autocorrect], so that suggestions are - /// only shown when [autocorrect] is true. On Android autocorrection and suggestion are controlled separately. - /// - /// See also: - /// * + @override final bool enableSuggestions; - /// The minimum number of lines to occupy when the content spans fewer lines. - /// - /// This affects the height of the field itself and does not limit the number of lines that can be entered into the field. - /// - /// If this is null (default), text container starts with enough vertical space for one line and grows to accommodate - /// additional lines as they are entered. - /// - /// This can be used in combination with [maxLines] for a varying set of behaviors. - /// - /// If the value is set, it must be greater than zero. If the value is greater than 1, [maxLines] should also be set - /// to either null or greater than this value. - /// - /// When [maxLines] is set as well, the height will grow between the indicated range of lines. When [maxLines] is null, - /// it will grow as high as needed, starting from [minLines]. - /// - /// A few examples of behaviors possible with [minLines] and [maxLines] are as follows. These apply equally to - /// [TextField], [TextFormField], [CupertinoTextField], and [EditableText]. - /// - /// Input that always occupies at least 2 lines and has an infinite max. Expands vertically as needed. - /// ```dart - /// TextField(minLines: 2) - /// ``` - /// - /// Input whose height starts from 2 lines and grows up to 4 lines at which point the height limit is reached. - /// If additional lines are entered it will scroll vertically. - /// ```dart - /// const TextField(minLines:2, maxLines: 4) - /// ``` - /// - /// Defaults to null. - /// - /// See also: - /// * [maxLines], which sets the maximum number of lines visible, and has several examples of how minLines and - /// maxLines interact to produce various behaviors. - /// * [expands], which determines whether the field should fill the height of its parent. + @override final int? minLines; - /// The maximum number of lines to show at one time, wrapping if necessary. - /// - /// This affects the height of the field itself and does not limit the number of lines that can be entered into the - /// field. - /// - /// If this is 1 (the default), the text will not wrap, but will scroll horizontally instead. - /// - /// If this is null, there is no limit to the number of lines, and the text container will start with enough vertical - /// space for one line and automatically grow to accommodate additional lines as they are entered, up to the height of - /// its constraints. - /// - /// If this is not null, the value must be greater than zero, and it will lock the input to the given number of lines - /// and take up enough horizontal space to accommodate that number of lines. Setting [minLines] as well allows the - /// input to grow and shrink between the indicated range. - /// - /// The full set of behaviors possible with [minLines] and [maxLines] are as follows. These examples apply equally to - /// [TextField], [TextFormField], [CupertinoTextField], and [EditableText]. - /// - /// Input that occupies a single line and scrolls horizontally as needed. - /// ```dart - /// const TextField() - /// ``` - /// - /// Input whose height grows from one line up to as many lines as needed for the text that was entered. If a height - /// limit is imposed by its parent, it will scroll vertically when its height reaches that limit. - /// ```dart - /// const TextField(maxLines: null) - /// ``` - /// - /// The input's height is large enough for the given number of lines. If additional lines are entered the input scrolls - /// vertically. - /// ```dart - /// const TextField(maxLines: 2) - /// ``` - /// - /// Input whose height grows with content between a min and max. An infinite max is possible with `maxLines: null`. - /// ```dart - /// const TextField(minLines: 2, maxLines: 4) - /// ``` - /// - /// See also: - /// * [minLines], which sets the minimum number of lines visible. - /// * [expands], which determines whether the field should fill the height of its parent. + @override final int? maxLines; - /// Whether this widget's height will be sized to fill its parent. Defaults to false. - /// - /// If set to true and wrapped in a parent widget like [Expanded] or [SizedBox], the input will expand to fill the - /// parent. - /// - /// [maxLines] and [minLines] must both be null when this is set to true, otherwise an error is thrown. - /// - /// See the examples in [maxLines] for the complete picture of how [maxLines], [minLines], and [expands] interact to - /// produce various behaviors. - /// - /// Input that matches the height of its parent: - /// ```dart - /// const Expanded( - /// child: FTextField(maxLines: null, expands: true), - /// ) - /// ``` + @override final bool expands; - /// Whether the text can be changed. Defaults to false. - /// - /// When this is set to true, the text cannot be modified by any shortcut or keyboard operation. The text is still - /// selectable. + @override final bool readOnly; - /// Whether to show cursor. - /// - /// The cursor refers to the blinking caret when this [FTextField] is focused. + @override final bool? showCursor; - /// The maximum number of characters (Unicode grapheme clusters) to allow in the text field. - /// - /// If set, a character counter will be displayed below the field showing how many characters have been entered. If - /// set to a number greater than 0, it will also display the maximum number allowed. If set to [TextField.noMaxLength] - /// then only the current character count is displayed. - /// - /// After [maxLength] characters have been input, additional input is ignored, unless [maxLengthEnforcement] is set to - /// [MaxLengthEnforcement.none]. - /// - /// The text field enforces the length with a [LengthLimitingTextInputFormatter], which is evaluated after the supplied - /// [inputFormatters], if any. - /// - /// This value must be either null, [TextField.noMaxLength], or greater than 0. If null (the default) then there is no - /// limit to the number of characters that can be entered. If set to [TextField.noMaxLength], then no limit will be - /// enforced, but the number of characters entered will still be displayed. - /// - /// Whitespace characters (e.g. newline, space, tab) are included in the character count. - /// - /// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than [maxLength] characters may be entered, - /// but the error counter and divider will switch to the [style]'s [FTextFieldStyle.error] when the limit is exceeded. + @override final int? maxLength; - /// Determines how the [maxLength] limit should be enforced. + @override final MaxLengthEnforcement? maxLengthEnforcement; - /// Called when the user initiates a change to the TextField's value: when they have inserted or deleted text. - /// - /// This callback doesn't run when the TextField's text is changed programmatically, via the TextField's [controller]. - /// Typically it isn't necessary to be notified of such changes, since they're initiated by the app itself. - /// - /// To be notified of all changes to the TextField's text, cursor, and selection, one can add a listener to its - /// [controller] with [TextEditingController.addListener]. - /// - /// [onChange] is called before [onSubmit] when user indicates completion of editing, such as when pressing the "done" - /// button on the keyboard. That default behavior can be overridden. See [onEditingComplete] for details. - /// - /// See also: - /// * [inputFormatters], which are called before [onChange] runs and can validate and change ("format") the input value. - /// * [onEditingComplete], [onSubmit]: which are more specialized input change notifications. + @override final ValueChanged? onChange; - /// Called when the user submits editable content (e.g., user presses the "done" button on the keyboard). - /// - /// The default implementation of [onEditingComplete] executes 2 different behaviors based on the situation: - /// - /// - When a completion action is pressed, such as "done", "go", "send", or "search", the user's content is submitted - /// to the [controller] and then focus is given up. - /// - /// - When a non-completion action is pressed, such as "next" or "previous", the user's content is submitted to the - /// [controller], but focus is not given up because developers may want to immediately move focus to another input - /// widget within [onSubmit]. - /// - /// Providing [onEditingComplete] prevents the aforementioned default behavior. + @override final VoidCallback? onEditingComplete; - /// Called when the user indicates that they are done editing the text in the field. - /// - /// By default, [onSubmit] is called after [onChange] when the user has finalized editing; or, if the default behavior - /// has been overridden, after [onEditingComplete]. See [onEditingComplete] for details. - /// - /// ## Testing - /// The following is the recommended way to trigger [onSubmit] in a test: - /// - /// ```dart - /// await tester.testTextInput.receiveAction(TextInputAction.done); - /// ``` - /// - /// Sending a `LogicalKeyboardKey.enter` via `tester.sendKeyEvent` will not trigger [onSubmit]. This is because on a - /// real device, the engine translates the enter key to a done action, but `tester.sendKeyEvent` sends the key to the - /// framework only. + @override final ValueChanged? onSubmit; - /// This is used to receive a private command from the input method. - /// - /// Called when the result of [TextInputClient.performPrivateCommand] is received. - /// - /// This can be used to provide domain-specific features that are only known between certain input methods and their - /// clients. - /// - /// See also: - /// * [performPrivateCommand](https://developer.android.com/reference/android/view/inputmethod/InputConnection#performPrivateCommand\(java.lang.String,%20android.os.Bundle\)), - /// which is the Android documentation for performPrivateCommand, used to send a command from the input method. - /// * [sendAppPrivateCommand](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#sendAppPrivateCommand), - /// which is the Android documentation for sendAppPrivateCommand, used to send a command to the input method. + @override final AppPrivateCommandCallback? onAppPrivateCommand; - /// Optional input validation and formatting overrides. - /// - /// Formatters are run in the provided order when the user changes the text this widget contains. When this parameter - /// changes, the new formatters will not be applied until the next time the user inserts or deletes text. Similar to - /// the [onChange] callback, formatters don't run when the text is changed programmatically via [controller]. - /// - /// See also: - /// * [TextEditingController], which implements the [Listenable] interface and notifies its listeners on - /// [TextEditingValue] changes. + @override final List? inputFormatters; - /// If false the text field is "disabled": it ignores taps. Defaults to true. + @override final bool enabled; - /// Determines whether this widget ignores pointer events. Defaults to null, and when null, does nothing. + @override final bool? ignorePointers; - /// Whether to enable user interface affordances for changing the text selection. Defaults to true. - /// - /// For example, setting this to true will enable features such as long-pressing the TextField to select text and show - /// the cut/copy/paste menu, and tapping to move the text caret. - /// - /// When this is false, the text selection cannot be adjusted by the user, text cannot be copied, and the user cannot - /// paste into the text field from the clipboard. + @override final bool enableInteractSelection; - /// Optional delegate for building the text selection handles. - /// - /// Historically, this field also controlled the toolbar. This is now handled by [contextMenuBuilder] instead. However, - /// for backwards compatibility, when [selectionControls] is set to an object that does not mix in - // ignore: deprecated_member_use - /// [TextSelectionHandleControls], [contextMenuBuilder] is ignored and the [TextSelectionControls.buildToolbar] method - /// is used instead. + @override final TextSelectionControls? selectionControls; - /// Determines the way that drag start behavior is handled. By default, the drag start behavior is [DragStartBehavior.start]. - /// - /// If set to [DragStartBehavior.start], scrolling drag behavior will begin at the position where the drag gesture won - /// the arena. If set to [DragStartBehavior.down] it will begin at the position where a down event is first detected. - /// - /// In general, setting this to [DragStartBehavior.start] will make drag animation smoother and setting it to - /// [DragStartBehavior.down] will make drag behavior feel slightly more reactive. - /// - /// See also: - /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + @override final DragStartBehavior dragStartBehavior; - // TODO: MouseCursor? mouseCursor; - - // TODO: InputCounterWidgetBuilder? buildCounter; - - /// The [ScrollPhysics] to use when vertically scrolling the input. If not specified, it will behave according to the - /// current platform. - /// - /// See [Scrollable.physics]. + @override final ScrollPhysics? scrollPhysics; - /// The [ScrollController] to use when vertically scrolling the input. If null, it will instantiate a new ScrollController. - /// - /// See [Scrollable.controller]. + @override final ScrollController? scrollController; - /// A list of strings that helps the autofill service identify the type of this text input. - /// - /// See [TextField.autofillHints] for more information. + @override final Iterable? autofillHints; - /// Restoration ID to save and restore the state of the text field. - /// - /// See [TextField.restorationId] for more information. + @override final String? restorationId; - /// Whether iOS 14 Scribble features are enabled for this Defaults to true. - /// - /// Only available on iPads. + @override final bool scribbleEnabled; - /// Whether to enable that the IME update personalized data such as typing history and user dictionary data. - /// - /// See [TextField.enableIMEPersonalizedLearning] for more information. + @override final bool enableIMEPersonalizedLearning; - // TODO: ContentInsertionConfiguration? contentInsertionConfiguration - - /// Builds the text selection toolbar when requested by the user. - /// - /// See [TextField.contextMenuBuilder] for more information. + @override final EditableTextContextMenuBuilder? contextMenuBuilder; - /// Determine whether this text field can request the primary focus. - /// - /// Defaults to true. If false, the text field will not request focus when tapped, or when its context menu is - /// displayed. If false it will not be possible to move the focus to the text field with tab key. + @override final bool canRequestFocus; - /// Controls the undo state. - /// - /// If null, this widget will create its own [UndoHistoryController]. + @override final UndoHistoryController? undoController; - /// Configuration that details how spell check should be performed. - /// - /// Specifies the [SpellCheckService] used to spell check text input and the [TextStyle] used to style text with - /// misspelled words. - /// - /// If the [SpellCheckService] is left null, spell check is disabled by default unless the [DefaultSpellCheckService] - /// is supported, in which case it is used. It is currently supported only on Android and iOS. - /// - /// If this configuration is left null, then spell check is disabled by default. + @override final SpellCheckConfiguration? spellCheckConfiguration; - /// The suffix icon. - /// - /// See [InputDecoration.suffixIcon] for more information. + @override final Widget? suffixIcon; /// Creates a [FTextField]. @@ -544,6 +250,7 @@ final class FTextField extends StatelessWidget { this.undoController, this.spellCheckConfiguration, this.suffixIcon, + super.key, }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'), assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'), assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.'); @@ -605,6 +312,7 @@ final class FTextField extends StatelessWidget { this.undoController, this.spellCheckConfiguration, this.suffixIcon, + super.key, }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'), assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'), assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.'); @@ -675,6 +383,7 @@ final class FTextField extends StatelessWidget { this.undoController, this.spellCheckConfiguration, this.suffixIcon, + super.key, }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'), assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'), assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.'); @@ -744,6 +453,7 @@ final class FTextField extends StatelessWidget { this.undoController, this.spellCheckConfiguration, this.suffixIcon, + super.key, }) : assert(label == null || rawLabel == null, 'Cannot provide both a label and a rawLabel.'), assert(help == null || rawHelp == null, 'Cannot provide both a help and a rawHelp.'), assert(error == null || rawError == null, 'Cannot provide both an error and a rawError.'); @@ -985,7 +695,8 @@ final class FTextField extends StatelessWidget { ..add(IterableProperty('autofillHints', autofillHints)) ..add(StringProperty('restorationId', restorationId)) ..add(FlagProperty('scribbleEnabled', value: scribbleEnabled, ifTrue: 'scribbleEnabled')) - ..add(FlagProperty( + ..add( + FlagProperty( 'enableIMEPersonalizedLearning', value: enableIMEPersonalizedLearning, ifTrue: 'enableIMEPersonalizedLearning', diff --git a/forui/lib/src/widgets/text_field/text_field_mixin.dart b/forui/lib/src/widgets/text_field/text_field_mixin.dart new file mode 100644 index 000000000..1aaa31a85 --- /dev/null +++ b/forui/lib/src/widgets/text_field/text_field_mixin.dart @@ -0,0 +1,466 @@ +part of 'text_field.dart'; + + +@internal +// TODO: https://github.com/dart-lang/sdk/issues/56093 +// ignore: public_member_api_docs +Widget defaultContextMenuBuilder( + BuildContext context, + EditableTextState state, + ) => AdaptiveTextSelectionToolbar.editableText(editableTextState: state); + +@internal +mixin FTextFieldMixin { + /// The text field's style. Defaults to [FThemeData.textFieldStyle]. + FTextFieldStyle? get style; + + /// The label above a text field. + /// + /// ## Contract: + /// Throws [AssertionError] if: + /// * both [label] and [rawLabel] are not null + String? get label; + + /// The raw label above a text field. + /// + /// ## Contract: + /// Throws [AssertionError] if: + /// * both [label] and [rawLabel] are not null + Widget? get rawLabel; + + /// The text to display when the text field is empty. + /// + /// See [InputDecoration.hintText] for more information. + String? get hint; + + /// The maximum number of lines the [hint] can occupy. Defaults to the value of [TextField.maxLines] attribute. + /// + /// See [InputDecoration.hintMaxLines] for more information. + int? get hintMaxLines; + + /// The help text. + /// + /// See [InputDecoration.helperText] for more information. + String? get help; + + /// The raw help text. + Widget? get rawHelp; + + /// The maximum number of lines the [help] can occupy. Defaults to the value of [TextField.maxLines] attribute. + /// + /// See [InputDecoration.helperMaxLines] for more information. + int? get helpMaxLines; + + /// The error text. + /// + /// See [InputDecoration.errorText] for more information. + String? get error; + + /// The raw error text. + Widget? get rawError; + + /// The maximum number of lines the [error] can occupy. Defaults to the value of [TextField.maxLines] attribute. + /// + /// See [InputDecoration.errorMaxLines] for more information. + int? get errorMaxLines; + + /// The configuration for the magnifier of this text field. + /// + /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] on Android, and builds nothing on all + /// other platforms. To suppress the magnifier, consider passing [TextMagnifierConfiguration.disabled]. + TextMagnifierConfiguration? get magnifierConfiguration; + + /// Controls the text being edited. If null, this widget will create its own [TextEditingController]. + TextEditingController? get controller; + + /// Defines the keyboard focus for this + /// + /// See [TextField.focusNode] for more information. + FocusNode? get focusNode; + + /// The type of keyboard to use for editing the text. Defaults to [TextInputType.text] if maxLines is one and + /// [TextInputType.multiline] otherwise. + TextInputType? get keyboardType; + + /// The type of action button to use for the keyboard. + /// + /// Defaults to [TextInputAction.newline] if [keyboardType] is [TextInputType.multiline] and [TextInputAction.done] + /// otherwise. + TextInputAction? get textInputAction; + + /// Configures how the platform keyboard will select an uppercase or lowercase keyboard. Defaults to + /// [TextCapitalization.none]. + /// + /// Only supports text keyboards, other keyboard types will ignore this configuration. Capitalization is locale-aware. + /// + /// See [TextCapitalization] for a description of each capitalization behavior. + TextCapitalization get textCapitalization; + + /// How the text should be aligned horizontally. + /// + /// Defaults to [TextAlign.start]. + TextAlign get textAlign; + + /// How the text should be aligned vertically. + /// + /// See [TextAlignVertical] for more information. + TextAlignVertical? get textAlignVertical; + + /// The directionality of the text. Defaults to the ambient [Directionality], if any. + /// + /// See [TextField.textDirection] for more information. + TextDirection? get textDirection; + + /// Whether this text field should focus itself if nothing else is already focused. Defaults to false. + /// + /// If true, the keyboard will open as soon as this text field obtains focus. Otherwise, the keyboard is only shown + /// after the user taps the text field. + bool get autofocus; + + /// Represents the interactive "state" of this widget in terms of a set of [WidgetState]s, including + /// [WidgetState.disabled], [WidgetState.hovered], [WidgetState.error], and [WidgetState.focused]. + /// + /// See [TextField.statesController] for more information. + WidgetStatesController? get statesController; + + /// Whether to hide the text being edited (e.g., for passwords). Defaults to false. + /// + /// When this is set to true, all the characters in the text field are obscured, and the text in the field cannot be + /// copied with copy or cut. If [readOnly] is also true, then the text cannot be selected. + bool get obscureText; + + /// Whether to enable autocorrection. Defaults to true. + bool get autocorrect; + + /// Whether to allow the platform to automatically format dashes. + /// + /// See [TextField.smartDashesType] for more information. + SmartDashesType? get smartDashesType; + + /// Whether to allow the platform to automatically format quotes. + /// + /// See [TextField.smartQuotesType] for more information. + SmartQuotesType? get smartQuotesType; + + /// Whether to show input suggestions as the user types. Defaults to true. + /// + /// This flag only affects Android. On iOS, suggestions are tied directly to [autocorrect], so that suggestions are + /// only shown when [autocorrect] is true. On Android autocorrection and suggestion are controlled separately. + /// + /// See also: + /// * + bool get enableSuggestions; + + /// The minimum number of lines to occupy when the content spans fewer lines. + /// + /// This affects the height of the field itself and does not limit the number of lines that can be entered into the field. + /// + /// If this is null (default), text container starts with enough vertical space for one line and grows to accommodate + /// additional lines as they are entered. + /// + /// This can be used in combination with [maxLines] for a varying set of behaviors. + /// + /// If the value is set, it must be greater than zero. If the value is greater than 1, [maxLines] should also be set + /// to either null or greater than this value. + /// + /// When [maxLines] is set as well, the height will grow between the indicated range of lines. When [maxLines] is null, + /// it will grow as high as needed, starting from [minLines]. + /// + /// A few examples of behaviors possible with [minLines] and [maxLines] are as follows. These apply equally to + /// [TextField], [TextFormField], [CupertinoTextField], and [EditableText]. + /// + /// Input that always occupies at least 2 lines and has an infinite max. Expands vertically as needed. + /// ```dart + /// TextField(minLines: 2) + /// ``` + /// + /// Input whose height starts from 2 lines and grows up to 4 lines at which point the height limit is reached. + /// If additional lines are entered it will scroll vertically. + /// ```dart + /// const TextField(minLines:2, maxLines: 4) + /// ``` + /// + /// Defaults to null. + /// + /// See also: + /// * [maxLines], which sets the maximum number of lines visible, and has several examples of how minLines and + /// maxLines interact to produce various behaviors. + /// * [expands], which determines whether the field should fill the height of its parent. + int? get minLines; + + /// The maximum number of lines to show at one time, wrapping if necessary. + /// + /// This affects the height of the field itself and does not limit the number of lines that can be entered into the + /// field. + /// + /// If this is 1 (the default), the text will not wrap, but will scroll horizontally instead. + /// + /// If this is null, there is no limit to the number of lines, and the text container will start with enough vertical + /// space for one line and automatically grow to accommodate additional lines as they are entered, up to the height of + /// its constraints. + /// + /// If this is not null, the value must be greater than zero, and it will lock the input to the given number of lines + /// and take up enough horizontal space to accommodate that number of lines. Setting [minLines] as well allows the + /// input to grow and shrink between the indicated range. + /// + /// The full set of behaviors possible with [minLines] and [maxLines] are as follows. These examples apply equally to + /// [TextField], [TextFormField], [CupertinoTextField], and [EditableText]. + /// + /// Input that occupies a single line and scrolls horizontally as needed. + /// ```dart + /// const TextField() + /// ``` + /// + /// Input whose height grows from one line up to as many lines as needed for the text that was entered. If a height + /// limit is imposed by its parent, it will scroll vertically when its height reaches that limit. + /// ```dart + /// const TextField(maxLines: null) + /// ``` + /// + /// The input's height is large enough for the given number of lines. If additional lines are entered the input scrolls + /// vertically. + /// ```dart + /// const TextField(maxLines: 2) + /// ``` + /// + /// Input whose height grows with content between a min and max. An infinite max is possible with `maxLines: null`. + /// ```dart + /// const TextField(minLines: 2, maxLines: 4) + /// ``` + /// + /// See also: + /// * [minLines], which sets the minimum number of lines visible. + /// * [expands], which determines whether the field should fill the height of its parent. + int? get maxLines; + + /// Whether this widget's height will be sized to fill its parent. Defaults to false. + /// + /// If set to true and wrapped in a parent widget like [Expanded] or [SizedBox], the input will expand to fill the + /// parent. + /// + /// [maxLines] and [minLines] must both be null when this is set to true, otherwise an error is thrown. + /// + /// See the examples in [maxLines] for the complete picture of how [maxLines], [minLines], and [expands] interact to + /// produce various behaviors. + /// + /// Input that matches the height of its parent: + /// ```dart + /// const Expanded( + /// child: FTextField(maxLines: null, expands: true), + /// ) + /// ``` + bool get expands; + + /// Whether the text can be changed. Defaults to false. + /// + /// When this is set to true, the text cannot be modified by any shortcut or keyboard operation. The text is still + /// selectable. + bool get readOnly; + + /// Whether to show cursor. + /// + /// The cursor refers to the blinking caret when this [FTextField] is focused. + bool? get showCursor; + + /// The maximum number of characters (Unicode grapheme clusters) to allow in the text field. + /// + /// If set, a character counter will be displayed below the field showing how many characters have been entered. If + /// set to a number greater than 0, it will also display the maximum number allowed. If set to [TextField.noMaxLength] + /// then only the current character count is displayed. + /// + /// After [maxLength] characters have been input, additional input is ignored, unless [maxLengthEnforcement] is set to + /// [MaxLengthEnforcement.none]. + /// + /// The text field enforces the length with a [LengthLimitingTextInputFormatter], which is evaluated after the supplied + /// [inputFormatters], if any. + /// + /// This value must be either null, [TextField.noMaxLength], or greater than 0. If null (the default) then there is no + /// limit to the number of characters that can be entered. If set to [TextField.noMaxLength], then no limit will be + /// enforced, but the number of characters entered will still be displayed. + /// + /// Whitespace characters (e.g. newline, space, tab) are included in the character count. + /// + /// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than [maxLength] characters may be entered, + /// but the error counter and divider will switch to the [style]'s [FTextFieldStyle.error] when the limit is exceeded. + int? get maxLength; + + /// Determines how the [maxLength] limit should be enforced. + MaxLengthEnforcement? get maxLengthEnforcement; + + /// Called when the user initiates a change to the TextField's value: when they have inserted or deleted text. + /// + /// This callback doesn't run when the TextField's text is changed programmatically, via the TextField's [controller]. + /// Typically it isn't necessary to be notified of such changes, since they're initiated by the app itself. + /// + /// To be notified of all changes to the TextField's text, cursor, and selection, one can add a listener to its + /// [controller] with [TextEditingController.addListener]. + /// + /// [onChange] is called before [onSubmit] when user indicates completion of editing, such as when pressing the "done" + /// button on the keyboard. That default behavior can be overridden. See [onEditingComplete] for details. + /// + /// See also: + /// * [inputFormatters], which are called before [onChange] runs and can validate and change ("format") the input value. + /// * [onEditingComplete], [onSubmit]: which are more specialized input change notifications. + ValueChanged? get onChange; + + /// Called when the user submits editable content (e.g., user presses the "done" button on the keyboard). + /// + /// The default implementation of [onEditingComplete] executes 2 different behaviors based on the situation: + /// + /// - When a completion action is pressed, such as "done", "go", "send", or "search", the user's content is submitted + /// to the [controller] and then focus is given up. + /// + /// - When a non-completion action is pressed, such as "next" or "previous", the user's content is submitted to the + /// [controller], but focus is not given up because developers may want to immediately move focus to another input + /// widget within [onSubmit]. + /// + /// Providing [onEditingComplete] prevents the aforementioned default behavior. + VoidCallback? get onEditingComplete; + + /// Called when the user indicates that they are done editing the text in the field. + /// + /// By default, [onSubmit] is called after [onChange] when the user has finalized editing; or, if the default behavior + /// has been overridden, after [onEditingComplete]. See [onEditingComplete] for details. + /// + /// ## Testing + /// The following is the recommended way to trigger [onSubmit] in a test: + /// + /// ```dart + /// await tester.testTextInput.receiveAction(TextInputAction.done); + /// ``` + /// + /// Sending a `LogicalKeyboardKey.enter` via `tester.sendKeyEvent` will not trigger [onSubmit]. This is because on a + /// real device, the engine translates the enter key to a done action, but `tester.sendKeyEvent` sends the key to the + /// framework only. + ValueChanged? get onSubmit; + + /// This is used to receive a private command from the input method. + /// + /// Called when the result of [TextInputClient.performPrivateCommand] is received. + /// + /// This can be used to provide domain-specific features that are only known between certain input methods and their + /// clients. + /// + /// See also: + /// * [performPrivateCommand](https://developer.android.com/reference/android/view/inputmethod/InputConnection#performPrivateCommand\(java.lang.String,%20android.os.Bundle\)), + /// which is the Android documentation for performPrivateCommand, used to send a command from the input method. + /// * [sendAppPrivateCommand](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#sendAppPrivateCommand), + /// which is the Android documentation for sendAppPrivateCommand, used to send a command to the input method. + AppPrivateCommandCallback? get onAppPrivateCommand; + + /// Optional input validation and formatting overrides. + /// + /// Formatters are run in the provided order when the user changes the text this widget contains. When this parameter + /// changes, the new formatters will not be applied until the next time the user inserts or deletes text. Similar to + /// the [onChange] callback, formatters don't run when the text is changed programmatically via [controller]. + /// + /// See also: + /// * [TextEditingController], which implements the [Listenable] interface and notifies its listeners on + /// [TextEditingValue] changes. + List? get inputFormatters; + + /// If false the text field is "disabled": it ignores taps. Defaults to true. + bool get enabled; + + /// Determines whether this widget ignores pointer events. Defaults to null, and when null, does nothing. + bool? get ignorePointers; + + /// Whether to enable user interface affordances for changing the text selection. Defaults to true. + /// + /// For example, setting this to true will enable features such as long-pressing the TextField to select text and show + /// the cut/copy/paste menu, and tapping to move the text caret. + /// + /// When this is false, the text selection cannot be adjusted by the user, text cannot be copied, and the user cannot + /// paste into the text field from the clipboard. + bool get enableInteractSelection; + + /// Optional delegate for building the text selection handles. + /// + /// Historically, this field also controlled the toolbar. This is now handled by [contextMenuBuilder] instead. However, + /// for backwards compatibility, when [selectionControls] is set to an object that does not mix in + // ignore: deprecated_member_use + /// [TextSelectionHandleControls], [contextMenuBuilder] is ignored and the [TextSelectionControls.buildToolbar] method + /// is used instead. + TextSelectionControls? get selectionControls; + + /// Determines the way that drag start behavior is handled. By default, the drag start behavior is [DragStartBehavior.start]. + /// + /// If set to [DragStartBehavior.start], scrolling drag behavior will begin at the position where the drag gesture won + /// the arena. If set to [DragStartBehavior.down] it will begin at the position where a down event is first detected. + /// + /// In general, setting this to [DragStartBehavior.start] will make drag animation smoother and setting it to + /// [DragStartBehavior.down] will make drag behavior feel slightly more reactive. + /// + /// See also: + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + DragStartBehavior get dragStartBehavior; + + // TODO: MouseCursor? mouseCursor; + + // TODO: InputCounterWidgetBuilder? buildCounter; + + /// The [ScrollPhysics] to use when vertically scrolling the input. If not specified, it will behave according to the + /// current platform. + /// + /// See [Scrollable.physics]. + ScrollPhysics? get scrollPhysics; + + /// The [ScrollController] to use when vertically scrolling the input. If null, it will instantiate a new ScrollController. + /// + /// See [Scrollable.controller]. + ScrollController? get scrollController; + + /// A list of strings that helps the autofill service identify the type of this text input. + /// + /// See [TextField.autofillHints] for more information. + Iterable? get autofillHints; + + /// Restoration ID to save and restore the state of the text field. + /// + /// See [TextField.restorationId] for more information. + String? get restorationId; + + /// Whether iOS 14 Scribble features are enabled for this Defaults to true. + /// + /// Only available on iPads. + bool get scribbleEnabled; + + /// Whether to enable that the IME update personalized data such as typing history and user dictionary data. + /// + /// See [TextField.enableIMEPersonalizedLearning] for more information. + bool get enableIMEPersonalizedLearning; + + // TODO: ContentInsertionConfiguration? contentInsertionConfiguration + + /// Builds the text selection toolbar when requested by the user. + /// + /// See [TextField.contextMenuBuilder] for more information. + EditableTextContextMenuBuilder? get contextMenuBuilder; + + /// Determine whether this text field can request the primary focus. + /// + /// Defaults to true. If false, the text field will not request focus when tapped, or when its context menu is + /// displayed. If false it will not be possible to move the focus to the text field with tab key. + bool get canRequestFocus; + + /// Controls the undo state. + /// + /// If null, this widget will create its own [UndoHistoryController]. + UndoHistoryController? get undoController; + + /// Configuration that details how spell check should be performed. + /// + /// Specifies the [SpellCheckService] used to spell check text input and the [TextStyle] used to style text with + /// misspelled words. + /// + /// If the [SpellCheckService] is left null, spell check is disabled by default unless the [DefaultSpellCheckService] + /// is supported, in which case it is used. It is currently supported only on Android and iOS. + /// + /// If this configuration is left null, then spell check is disabled by default. + SpellCheckConfiguration? get spellCheckConfiguration; + + /// The suffix icon. + /// + /// See [InputDecoration.suffixIcon] for more information. + Widget? get suffixIcon; +} diff --git a/forui/lib/theme.dart b/forui/lib/theme.dart index 1cc6bde09..b0799fc43 100644 --- a/forui/lib/theme.dart +++ b/forui/lib/theme.dart @@ -2,11 +2,9 @@ /// choices of Forui widgets. library forui.theme; -import 'package:forui/theme.dart'; - export 'src/theme/color_scheme.dart'; export 'src/theme/style.dart'; export 'src/theme/theme.dart'; export 'src/theme/theme_data.dart'; -export 'src/theme/typography.dart'; export 'src/theme/themes.dart'; +export 'src/theme/typography.dart'; diff --git a/forui/lib/widgets.dart b/forui/lib/widgets.dart index 67b0e442e..6eed023aa 100644 --- a/forui/lib/widgets.dart +++ b/forui/lib/widgets.dart @@ -5,9 +5,10 @@ export 'src/widgets/badge/badge.dart' hide FBadgeContent, Variant; export 'src/widgets/button/button.dart' hide FButtonContent, Variant; export 'src/widgets/card/card.dart' hide FCardContent; export 'src/widgets/dialog/dialog.dart' hide FDialogContent, FHorizontalDialogContent, FVerticalDialogContent; +export 'src/widgets/form/text_form_field.dart'; export 'src/widgets/header/header.dart'; export 'src/widgets/tabs/tabs.dart'; -export 'src/widgets/text_field/text_field.dart' hide defaultContextMenuBuilder; +export 'src/widgets/text_field/text_field.dart' hide FTextFieldMixin, defaultContextMenuBuilder; export 'src/widgets/scaffold.dart'; export 'src/widgets/separator.dart'; export 'src/widgets/switch.dart';