Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add AutoSuggestBox.form #353

Merged
merged 3 commits into from
May 21, 2022
Merged
Show file tree
Hide file tree
Changes from all 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Date format: DD/MM/YYYY
- Added `.initialValue`, `.selectionControls`, `.mouseCursor`, `.textDirection`, `.scribbleEnabled` and `.enableIMEPersonalizedLearning` to `TextBox`
- Added `AutoFillClient` to `TextBox`
- Added `UnmanagedRestorationScope` to `TextFormBox`
- Added `AutoSuggestBox.form`, that uses `TextFormBox` instead of `TextBox` ([#353](https://github.com/bdlukaa/fluent_ui/pull/353))

## [3.12.0] - Flutter 3.0 - [13/05/2022]

Expand Down
10 changes: 8 additions & 2 deletions example/lib/screens/forms.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class _FormsState extends State<Forms> {
),
]),
const SizedBox(height: 20),
Row(children: [
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expanded(
child: TextBox(
readOnly: true,
Expand All @@ -114,7 +114,13 @@ class _FormsState extends State<Forms> {
),
const SizedBox(width: 10),
Expanded(
child: AutoSuggestBox(
child: AutoSuggestBox.form(
autovalidateMode: AutovalidateMode.always,
validator: (t) {
if (t == null || t.isEmpty) return 'emtpy';

return null;
},
items: values,
placeholder: 'Pick a color',
trailingIcon: IconButton(
Expand Down
173 changes: 127 additions & 46 deletions lib/src/controls/form/auto_suggest_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ enum TextChangedReason {
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/windows/apps/design/controls/auto-suggest-box>
/// * [TextBox], which is used by this widget to enter user text input
/// * [Overlay], which is used to show the suggestion popup
/// * <https://docs.microsoft.com/en-us/windows/apps/design/controls/auto-suggest-box>
/// * [TextBox], which is used by this widget to enter user text input
/// * [TextFormBox], which is used by this widget by Form
/// * [Overlay], which is used to show the suggestion popup
class AutoSuggestBox extends StatefulWidget {
/// Creates a fluent-styled auto suggest box.
const AutoSuggestBox({
Expand All @@ -50,6 +51,36 @@ class AutoSuggestBox extends StatefulWidget {
this.scrollPadding = const EdgeInsets.all(20.0),
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
}) : autovalidateMode = AutovalidateMode.disabled,
validator = null,
super(key: key);

const AutoSuggestBox.form({
Key? key,
required this.items,
this.controller,
this.onChanged,
this.onSelected,
this.leadingIcon,
this.trailingIcon,
this.clearButtonEnabled = true,
this.placeholder,
this.placeholderStyle,
this.style,
this.decoration,
this.foregroundDecoration,
this.highlightColor,
this.cursorColor,
this.cursorHeight,
this.cursorRadius,
this.cursorWidth = 1.5,
this.showCursor,
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
this.validator,
this.autovalidateMode = AutovalidateMode.disabled,
}) : super(key: key);

/// The list of items to display to the user to pick
Expand Down Expand Up @@ -148,6 +179,14 @@ class AutoSuggestBox extends StatefulWidget {
/// {@macro flutter.widgets.editableText.scrollPadding}
final EdgeInsets scrollPadding;

/// An optional method that validates an input. Returns an error string to
/// display if the input is invalid, or null otherwise.
final FormFieldValidator<String>? validator;

/// Used to enable/disable this form field auto validation and update its
/// error text.
final AutovalidateMode autovalidateMode;

@override
_AutoSuggestBoxState createState() => _AutoSuggestBoxState();

Expand Down Expand Up @@ -184,7 +223,7 @@ class _AutoSuggestBoxState<T> extends State<AutoSuggestBox> {
late TextEditingController controller;
final FocusScopeNode overlayNode = FocusScopeNode();

final clearGlobalKey = GlobalKey();
Size _boxSize = Size.zero;

@override
void initState() {
Expand All @@ -193,6 +232,18 @@ class _AutoSuggestBoxState<T> extends State<AutoSuggestBox> {
controller.addListener(() {
if (!mounted) return;
if (controller.text.length < 2) setState(() {});

// Update the overlay when the text box size has changed
WidgetsBinding.instance.addPostFrameCallback((_) async {
final box = _textBoxKey.currentContext!.findRenderObject() as RenderBox;

if (_boxSize != box.size) {
_dismissOverlay();
setState(() {});
_showOverlay();
_boxSize = box.size;
}
});
});
focusNode.addListener(_handleFocusChanged);
}
Expand Down Expand Up @@ -274,60 +325,90 @@ class _AutoSuggestBoxState<T> extends State<AutoSuggestBox> {
}
}

void _onChanged(String text) {
widget.onChanged?.call(text, TextChangedReason.userInput);
_showOverlay();
}

/// Whether a [TextFormBox] is used instead of a [TextBox]
bool get useForm => widget.validator != null;

@override
Widget build(BuildContext context) {
assert(debugCheckHasFluentTheme(context));
assert(debugCheckHasFluentLocalizations(context));

final suffix = Row(mainAxisSize: MainAxisSize.min, children: [
if (widget.trailingIcon != null) widget.trailingIcon!,
if (widget.clearButtonEnabled && controller.text.isNotEmpty)
Padding(
padding: const EdgeInsetsDirectional.only(start: 2.0),
child: IconButton(
icon: const Icon(FluentIcons.chrome_close),
onPressed: () {
controller.clear();
focusNode.unfocus();
},
),
),
]);

return CompositedTransformTarget(
link: _layerLink,
child: Actions(
actions: {
DirectionalFocusIntent: _DirectionalFocusAction(),
},
child: TextBox(
key: _textBoxKey,
controller: controller,
focusNode: focusNode,
placeholder: widget.placeholder,
placeholderStyle: widget.placeholderStyle,
clipBehavior:
_entry != null ? Clip.none : Clip.antiAliasWithSaveLayer,
prefix: widget.leadingIcon,
clearGlobalKey: clearGlobalKey,
suffix: Row(mainAxisSize: MainAxisSize.min, children: [
if (widget.trailingIcon != null) widget.trailingIcon!,
if (widget.clearButtonEnabled && controller.text.isNotEmpty)
Padding(
padding: const EdgeInsetsDirectional.only(start: 2.0),
child: IconButton(
key: clearGlobalKey,
icon: const Icon(FluentIcons.chrome_close),
onPressed: () {
controller.clear();
focusNode.unfocus();
},
),
child: useForm
? TextFormBox(
key: _textBoxKey,
controller: controller,
focusNode: focusNode,
placeholder: widget.placeholder,
placeholderStyle: widget.placeholderStyle,
clipBehavior: Clip.antiAliasWithSaveLayer,
prefix: widget.leadingIcon,
suffix: suffix,
suffixMode: OverlayVisibilityMode.always,
onChanged: _onChanged,
style: widget.style,
decoration: widget.decoration,
highlightColor: widget.highlightColor,
cursorColor: widget.cursorColor,
cursorHeight: widget.cursorHeight,
cursorRadius: widget.cursorRadius ?? const Radius.circular(2.0),
cursorWidth: widget.cursorWidth,
showCursor: widget.showCursor,
scrollPadding: widget.scrollPadding,
selectionHeightStyle: widget.selectionHeightStyle,
selectionWidthStyle: widget.selectionWidthStyle,
validator: widget.validator,
autovalidateMode: widget.autovalidateMode,
)
: TextBox(
key: _textBoxKey,
controller: controller,
focusNode: focusNode,
placeholder: widget.placeholder,
placeholderStyle: widget.placeholderStyle,
clipBehavior: Clip.antiAliasWithSaveLayer,
prefix: widget.leadingIcon,
suffix: suffix,
suffixMode: OverlayVisibilityMode.always,
onChanged: _onChanged,
style: widget.style,
decoration: widget.decoration,
foregroundDecoration: widget.foregroundDecoration,
highlightColor: widget.highlightColor,
cursorColor: widget.cursorColor,
cursorHeight: widget.cursorHeight,
cursorRadius: widget.cursorRadius,
cursorWidth: widget.cursorWidth,
showCursor: widget.showCursor,
scrollPadding: widget.scrollPadding,
selectionHeightStyle: widget.selectionHeightStyle,
selectionWidthStyle: widget.selectionWidthStyle,
),
]),
suffixMode: OverlayVisibilityMode.always,
onChanged: (text) {
widget.onChanged?.call(text, TextChangedReason.userInput);
_showOverlay();
},
style: widget.style,
decoration: widget.decoration,
foregroundDecoration: widget.foregroundDecoration,
highlightColor: widget.highlightColor,
cursorColor: widget.cursorColor,
cursorHeight: widget.cursorHeight,
cursorRadius: widget.cursorRadius,
cursorWidth: widget.cursorWidth,
showCursor: widget.showCursor,
scrollPadding: widget.scrollPadding,
selectionHeightStyle: widget.selectionHeightStyle,
selectionWidthStyle: widget.selectionWidthStyle,
),
),
);
}
Expand Down
9 changes: 6 additions & 3 deletions lib/src/controls/form/text_form_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class TextFormBox extends FormField<String> {
bool enableIMEPersonalizedLearning = true,
MouseCursor? mouseCursor,
bool scribbleEnabled = true,
Color? highlightColor,
Color? errorHighlightColor,
}) : assert(initialValue == null || controller == null),
assert(obscuringCharacter.length == 1),
assert(maxLines == null || maxLines > 0),
Expand Down Expand Up @@ -135,8 +137,7 @@ class TextFormBox extends FormField<String> {
}

return FormRow(
padding:
EdgeInsets.only(bottom: (field.errorText == null) ? 16.0 : 0),
padding: EdgeInsets.zero,
error: (field.errorText == null) ? null : Text(field.errorText!),
child: UnmanagedRestorationScope(
bucket: field.bucket,
Expand Down Expand Up @@ -189,7 +190,9 @@ class TextFormBox extends FormField<String> {
prefixMode: prefixMode,
suffix: suffix,
suffixMode: suffixMode,
highlightColor: (field.errorText == null) ? null : Colors.red,
highlightColor: (field.errorText == null)
? highlightColor
: errorHighlightColor ?? Colors.red,
dragStartBehavior: dragStartBehavior,
minHeight: minHeight,
padding: padding,
Expand Down