From c5484cf62b10a1a3bdb7c8aab5d61d10a72a814d Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 12 May 2026 17:56:23 +0530 Subject: [PATCH 1/3] Update Stac parsers and core models --- docs/concepts/theming.mdx | 32 +++++++++ docs/styles/text_style.mdx | 9 +-- docs/widgets/dropdown_menu.mdx | 18 ++++- docs/widgets/icon_button.mdx | 20 ++++-- docs/widgets/text_field.mdx | 11 ++- docs/widgets/text_form_field.mdx | 72 +++++++++++++++---- .../stac_input_decoration_parser.dart | 14 ++++ .../stac_input_formatter_type_parser.dart | 2 + .../stac_text_decoration_line_parser.dart | 17 +++++ .../text/stac_text_style_parser.dart | 2 + .../theme/stac_button_style_parser.dart | 2 +- ...ating_action_button_theme_data_parser.dart | 4 ++ .../stac_dropdown_menu_parser.dart | 5 ++ .../stac_selectable_text_parser.dart | 1 + .../widgets/stac_text/stac_text_parser.dart | 1 + .../stac_text_form_field_parser.dart | 20 ++++-- .../stac/lib/src/utils/input_formatters.dart | 48 ++++++++++++- .../test/utils/input_formatters_test.dart | 28 ++++++++ .../stac_input_decoration.dart | 7 ++ .../stac_input_decoration.g.dart | 2 + .../stac_input_formatter.dart | 11 ++- .../stac_input_formatter.g.dart | 3 + .../text/stac_text_style/stac_text_style.dart | 10 +++ .../stac_text_style/stac_text_style.g.dart | 12 ++++ .../lib/foundation/text/stac_text_types.dart | 15 ++++ ...tac_floating_action_button_theme_data.dart | 10 +++ ...c_floating_action_button_theme_data.g.dart | 10 +++ 27 files changed, 351 insertions(+), 35 deletions(-) create mode 100644 packages/stac/lib/src/parsers/foundation/text/stac_text_decoration_line_parser.dart create mode 100644 packages/stac/test/utils/input_formatters_test.dart diff --git a/docs/concepts/theming.mdx b/docs/concepts/theming.mdx index 8110473b5..77dd3a8e2 100644 --- a/docs/concepts/theming.mdx +++ b/docs/concepts/theming.mdx @@ -230,3 +230,35 @@ StacButtonStyle( shape: StacRoundedRectangleBorder(borderRadius: StacBorderRadius.all(8)), ) ``` + +### Floating Action Button Theme + +Use `floatingActionButtonTheme` to customize colors, elevation, shape, icon sizing, and layout constraints for floating action buttons: + +```dart +StacFloatingActionButtonThemeData( + backgroundColor: '#95E183', + foregroundColor: '#050608', + elevation: 6, + shape: StacRoundedRectangleBorder(borderRadius: StacBorderRadius.all(16)), + sizeConstraints: StacBoxConstraints(minWidth: 56, minHeight: 56), +) +``` + +```json +{ + "floatingActionButtonTheme": { + "backgroundColor": "#95E183", + "foregroundColor": "#050608", + "elevation": 6, + "shape": { + "type": "roundedRectangleBorder", + "borderRadius": 16 + }, + "sizeConstraints": { + "minWidth": 56, + "minHeight": 56 + } + } +} +``` diff --git a/docs/styles/text_style.mdx b/docs/styles/text_style.mdx index 030aac89c..9f43e7990 100644 --- a/docs/styles/text_style.mdx +++ b/docs/styles/text_style.mdx @@ -19,8 +19,9 @@ The Stac TextStyle classes allow you to style text with fonts, colors, sizes, an | wordSpacing | `double?` | Spacing between words. | | textBaseline | `String?` | Baseline alignment. | | height | `double?` | Line height as multiple of font size. | +| decoration | `String?` | Decoration line: `none`, `underline`, `overline`, `lineThrough`. | | decorationColor | `String?` | Color for text decorations. | -| decorationStyle | `String?` | Decoration style: solid, dotted, dashed, wavy. | +| decorationStyle | `String?` | Decoration style: `solid`, `double`, `dotted`, `dashed`, `wavy`. | | decorationThickness | `double?` | Thickness of decorations. | | fontFamily | `String?` | Font family name. | | fontFamilyFallback | `List?` | Fallback font families. | @@ -124,7 +125,7 @@ StacColumn( StacText( data: 'Underlined Text', style: StacTextStyle( - decoration: StacTextDecoration.underline, + decoration: StacTextDecorationLine.underline, decorationColor: StacColors.red, decorationStyle: StacTextDecorationStyle.solid, ), @@ -132,13 +133,13 @@ StacColumn( StacText( data: 'Strikethrough Text', style: StacTextStyle( - decoration: StacTextDecoration.lineThrough, + decoration: StacTextDecorationLine.lineThrough, ), ), StacText( data: 'Wavy Underline', style: StacTextStyle( - decoration: StacTextDecoration.underline, + decoration: StacTextDecorationLine.underline, decorationStyle: StacTextDecorationStyle.wavy, decorationColor: StacColors.blue, ), diff --git a/docs/widgets/dropdown_menu.mdx b/docs/widgets/dropdown_menu.mdx index 2fcfdc725..42005bc5c 100644 --- a/docs/widgets/dropdown_menu.mdx +++ b/docs/widgets/dropdown_menu.mdx @@ -13,6 +13,12 @@ export const dropdownMenuPreviewJson = { }, "hintText": "Please select", "width": 200, + "inputFormatters": [ + { + "type": "deny", + "rule": "\\s" + } + ], "dropdownMenuEntries": [ { "value": "option1", @@ -65,6 +71,7 @@ the [official documentation](https://api.flutter.dev/flutter/material/DropdownMe | enableFilter | `bool` | Whether to enable filtering. Defaults to `true`. | | enableSearch | `bool` | Whether to enable search. Defaults to `true`. | | requestFocusOnTap | `bool` | Whether to request focus on tap. Defaults to `true`. | +| inputFormatters | `List` | Input formatters for editable dropdown text. Supports `allow`, `deny`, and `mask`. | ## DropdownMenuEntry @@ -93,6 +100,9 @@ StacDropdownMenu( label: StacText(data: 'Select an option'), hintText: 'Please select', width: 200, + inputFormatters: [ + StacInputFormatter(type: StacInputFormatterType.deny, rule: r'\s'), + ], dropdownMenuEntries: [ StacDropdownMenuEntry( value: 'option1', @@ -125,6 +135,12 @@ StacDropdownMenu( }, "hintText": "Please select", "width": 200, + "inputFormatters": [ + { + "type": "deny", + "rule": "\\s" + } + ], "dropdownMenuEntries": [ { "value": "option1", @@ -186,4 +202,4 @@ StacDropdownMenu( /> - \ No newline at end of file + diff --git a/docs/widgets/icon_button.mdx b/docs/widgets/icon_button.mdx index 440484f36..50f4daa62 100644 --- a/docs/widgets/icon_button.mdx +++ b/docs/widgets/icon_button.mdx @@ -32,7 +32,11 @@ export const iconButtonPreviewJson = { }, "style": { "backgroundColor": "#FFC107", - "foregroundColor": "#000000" + "foregroundColor": "#000000", + "shape": { + "type": "roundedRectangleBorder", + "borderRadius": 8 + } }, "isSelected": false, "selectedIcon": { @@ -95,7 +99,11 @@ StacIconButton( tooltip: 'Add Item', enableFeedback: true, constraints: StacBoxConstraints(minWidth: 48.0, minHeight: 48.0), - style: StacButtonStyle(backgroundColor: StacColors.amber, foregroundColor: StacColors.black), + style: StacButtonStyle( + backgroundColor: StacColors.amber, + foregroundColor: StacColors.black, + shape: StacRoundedRectangleBorder(borderRadius: 8), + ), isSelected: false, selectedIcon: StacIcon(icon: 'check'), icon: StacIcon(icon: 'add'), @@ -133,7 +141,11 @@ StacIconButton( }, "style": { "backgroundColor": "#FFC107", - "foregroundColor": "#000000" + "foregroundColor": "#000000", + "shape": { + "type": "roundedRectangleBorder", + "borderRadius": 8 + } }, "isSelected": false, "selectedIcon": { @@ -179,4 +191,4 @@ StacIconButton( /> - \ No newline at end of file + diff --git a/docs/widgets/text_field.mdx b/docs/widgets/text_field.mdx index e1d02237a..298fb8f69 100644 --- a/docs/widgets/text_field.mdx +++ b/docs/widgets/text_field.mdx @@ -51,7 +51,14 @@ The Stac TextField allows you to build a Flutter text field widget using JSON. T | cursorHeight | `double` | The height of the cursor. | | cursorColor | `StacColor` | The color of the cursor. | | hintText | `String` | The hint text to display when the text field is empty. | -| inputFormatters | `List` | The list of input formatters to apply to the text field. | + +## StacInputDecoration + +`decoration` supports Flutter input decoration fields such as `labelText`, `hintText`, prefix/suffix content, borders, fill colors, and `floatingLabelBehavior`. + +| Property | Type | Description | +|-----------------------|----------|--------------------------------------------------------------------| +| floatingLabelBehavior | `String` | Controls when the label floats. Supported values: `auto`, `always`, `never`. | ## Example @@ -121,4 +128,4 @@ StacTextField( /> - \ No newline at end of file + diff --git a/docs/widgets/text_form_field.mdx b/docs/widgets/text_form_field.mdx index 5e74ddd22..22db15141 100644 --- a/docs/widgets/text_form_field.mdx +++ b/docs/widgets/text_form_field.mdx @@ -7,12 +7,13 @@ import { PLAYGROUND_BASE_URL } from "/snippets/playground_base.mdx"; export const textFormFieldPreviewJson = { "type": "textFormField", - "id": "email", + "id": "date", "autovalidateMode": "onUserInteraction", - "validatorRules": [ + "inputFormatters": [ { - "rule": "isEmail", - "message": "Please enter a valid email" + "type": "mask", + "rule": "\\d", + "mask": "##/##/####" } ], "style": { @@ -21,7 +22,9 @@ export const textFormFieldPreviewJson = { "height": 1.5 }, "decoration": { - "hintText": "Email", + "labelText": "Date of birth", + "hintText": "DD/MM/YYYY", + "floatingLabelBehavior": "always", "filled": true, "fillColor": "#FFFFFF", "border": { @@ -78,6 +81,36 @@ To know more about the TextFormField widget in Flutter, refer to the [official d | inputFormatters | `List` | The list of input formatters to apply to the text. Defaults to an empty list. | | validatorRules | `List` | The list of validator rules to apply to the text. Defaults to an empty list. | +## StacInputDecoration + +`decoration` supports Flutter input decoration fields such as `labelText`, `hintText`, `prefixIcon`, `suffixIcon`, borders, fill colors, and `floatingLabelBehavior`. + +| Property | Type | Description | +|-----------------------|----------|--------------------------------------------------------------------| +| floatingLabelBehavior | `String` | Controls when the label floats. Supported values: `auto`, `always`, `never`. | + +## Input Formatters + +Use `inputFormatters` to restrict or transform user input. The supported formatter `type` values are `allow`, `deny`, and `mask`. + +| Property | Type | Description | +|----------|----------|-----------------------------------------------------------------------------| +| type | `String` | Formatter type: `allow`, `deny`, or `mask`. | +| rule | `String` | Regular expression used by the formatter. | +| mask | `String` | Mask pattern used when `type` is `mask`; `#` consumes matching input values. | + +```json +{ + "inputFormatters": [ + { + "type": "mask", + "rule": "\\d", + "mask": "##/##/####" + } + ] +} +``` + ## Example @@ -85,14 +118,20 @@ To know more about the TextFormField widget in Flutter, refer to the [official d ```dart StacTextFormField( - id: 'email', + id: 'date', autovalidateMode: StacAutovalidateMode.onUserInteraction, - validatorRules: [ - StacFormFieldValidator(rule: 'isEmail', message: 'Please enter a valid email'), + inputFormatters: [ + StacInputFormatter( + type: StacInputFormatterType.mask, + rule: r'\d', + mask: '##/##/####', + ), ], style: StacTextStyle(fontSize: 16, fontWeight: StacFontWeight.w400, height: 1.5), decoration: StacInputDecoration( - hintText: 'Email', + labelText: 'Date of birth', + hintText: 'DD/MM/YYYY', + floatingLabelBehavior: 'always', filled: true, fillColor: StacColors.white, border: StacOutlineInputBorder(borderRadius: 8, color: '#24151D29'), @@ -106,12 +145,13 @@ StacTextFormField( ```json { "type": "textFormField", - "id": "email", + "id": "date", "autovalidateMode": "onUserInteraction", - "validatorRules": [ + "inputFormatters": [ { - "rule": "isEmail", - "message": "Please enter a valid email" + "type": "mask", + "rule": "\\d", + "mask": "##/##/####" } ], "style": { @@ -120,7 +160,9 @@ StacTextFormField( "height": 1.5 }, "decoration": { - "hintText": "Email", + "labelText": "Date of birth", + "hintText": "DD/MM/YYYY", + "floatingLabelBehavior": "always", "filled": true, "fillColor": "#FFFFFF", "border": { @@ -164,4 +206,4 @@ StacTextFormField( /> - \ No newline at end of file + diff --git a/packages/stac/lib/src/parsers/foundation/decoration/stac_input_decoration_parser.dart b/packages/stac/lib/src/parsers/foundation/decoration/stac_input_decoration_parser.dart index 934bb4e80..41a8dad06 100644 --- a/packages/stac/lib/src/parsers/foundation/decoration/stac_input_decoration_parser.dart +++ b/packages/stac/lib/src/parsers/foundation/decoration/stac_input_decoration_parser.dart @@ -12,6 +12,7 @@ extension StacInputDecorationParser on StacInputDecoration { icon: icon?.parse(context), labelText: labelText, labelStyle: labelStyle?.parse(context), + floatingLabelBehavior: _parseFloatingLabelBehavior(floatingLabelBehavior), hintText: hintText, hintStyle: hintStyle?.parse(context), helperText: helperText, @@ -38,3 +39,16 @@ extension StacInputDecorationParser on StacInputDecoration { ); } } + +FloatingLabelBehavior? _parseFloatingLabelBehavior(String? behavior) { + switch (behavior) { + case 'always': + return FloatingLabelBehavior.always; + case 'never': + return FloatingLabelBehavior.never; + case 'auto': + return FloatingLabelBehavior.auto; + default: + return null; + } +} diff --git a/packages/stac/lib/src/parsers/foundation/forms/stac_input_formatter_type_parser.dart b/packages/stac/lib/src/parsers/foundation/forms/stac_input_formatter_type_parser.dart index 59798dac8..648a804c5 100644 --- a/packages/stac/lib/src/parsers/foundation/forms/stac_input_formatter_type_parser.dart +++ b/packages/stac/lib/src/parsers/foundation/forms/stac_input_formatter_type_parser.dart @@ -8,6 +8,8 @@ extension StacInputFormatterTypeCoreParser on StacInputFormatterType { return InputFormatterType.allow; case StacInputFormatterType.deny: return InputFormatterType.deny; + case StacInputFormatterType.mask: + return InputFormatterType.mask; } } } diff --git a/packages/stac/lib/src/parsers/foundation/text/stac_text_decoration_line_parser.dart b/packages/stac/lib/src/parsers/foundation/text/stac_text_decoration_line_parser.dart new file mode 100644 index 000000000..c21c53df8 --- /dev/null +++ b/packages/stac/lib/src/parsers/foundation/text/stac_text_decoration_line_parser.dart @@ -0,0 +1,17 @@ +import 'package:flutter/painting.dart'; +import 'package:stac_core/foundation/text/stac_text_types.dart'; + +extension StacTextDecorationLineParser on StacTextDecorationLine { + TextDecoration get parse { + switch (this) { + case StacTextDecorationLine.none: + return TextDecoration.none; + case StacTextDecorationLine.underline: + return TextDecoration.underline; + case StacTextDecorationLine.overline: + return TextDecoration.overline; + case StacTextDecorationLine.lineThrough: + return TextDecoration.lineThrough; + } + } +} diff --git a/packages/stac/lib/src/parsers/foundation/text/stac_text_style_parser.dart b/packages/stac/lib/src/parsers/foundation/text/stac_text_style_parser.dart index d88a502df..c94bb2fa3 100644 --- a/packages/stac/lib/src/parsers/foundation/text/stac_text_style_parser.dart +++ b/packages/stac/lib/src/parsers/foundation/text/stac_text_style_parser.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stac/src/parsers/foundation/text/stac_font_style_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_font_weight_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_text_baseline_parser.dart'; +import 'package:stac/src/parsers/foundation/text/stac_text_decoration_line_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_text_decoration_style_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_text_leading_distribution_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_text_overflow_parser.dart'; @@ -99,6 +100,7 @@ extension StacTextStyleParser on StacTextStyle { textBaseline: style.textBaseline?.parse, height: style.height, leadingDistribution: style.leadingDistribution?.parse, + decoration: style.decoration?.parse, decorationColor: style.decorationColor?.toColor(context), decorationStyle: style.decorationStyle?.parse, decorationThickness: style.decorationThickness, diff --git a/packages/stac/lib/src/parsers/theme/stac_button_style_parser.dart b/packages/stac/lib/src/parsers/theme/stac_button_style_parser.dart index 1692cb1d9..f8b30023d 100644 --- a/packages/stac/lib/src/parsers/theme/stac_button_style_parser.dart +++ b/packages/stac/lib/src/parsers/theme/stac_button_style_parser.dart @@ -122,7 +122,7 @@ extension StacButtonStyleParser on StacButtonStyle { maximumSize: maximumSize?.parse, iconSize: iconSize, side: side?.parse(context), - // shape: shape?.parse(context), + shape: shape?.parse(context), padding: padding?.parse, enabledMouseCursor: enabledMouseCursor?.parse, disabledMouseCursor: disabledMouseCursor?.parse, diff --git a/packages/stac/lib/src/parsers/theme/stac_floating_action_button_theme_data_parser.dart b/packages/stac/lib/src/parsers/theme/stac_floating_action_button_theme_data_parser.dart index d45fcd753..001dcd063 100644 --- a/packages/stac/lib/src/parsers/theme/stac_floating_action_button_theme_data_parser.dart +++ b/packages/stac/lib/src/parsers/theme/stac_floating_action_button_theme_data_parser.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stac/src/parsers/foundation/borders/stac_shape_border_parser.dart'; +import 'package:stac/src/parsers/foundation/geometry/stac_box_constraints_parser.dart'; import 'package:stac/src/parsers/foundation/geometry/stac_edge_insets_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_text_style_parser.dart'; import 'package:stac/src/utils/color_utils.dart'; @@ -20,8 +22,10 @@ extension StacFloatingActionThemeParser on StacFloatingActionButtonThemeData { hoverElevation: hoverElevation, disabledElevation: disabledElevation, highlightElevation: highlightElevation, + shape: shape?.parse(context), enableFeedback: enableFeedback, iconSize: iconSize, + sizeConstraints: sizeConstraints?.parse, extendedIconLabelSpacing: extendedIconLabelSpacing, extendedPadding: extendedPadding?.parse, extendedTextStyle: extendedTextStyle?.parse(context), diff --git a/packages/stac/lib/src/parsers/widgets/stac_dropdown_menu/stac_dropdown_menu_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_dropdown_menu/stac_dropdown_menu_parser.dart index ff1a2e447..6dea66696 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_dropdown_menu/stac_dropdown_menu_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_dropdown_menu/stac_dropdown_menu_parser.dart @@ -86,6 +86,11 @@ class _DropDownMenuWidgetState extends State<_DropDownMenuWidget> { return InputFormatterType.allow.format(formatter.rule ?? ''); case StacInputFormatterType.deny: return InputFormatterType.deny.format(formatter.rule ?? ''); + case StacInputFormatterType.mask: + return InputFormatterType.mask.format( + formatter.rule ?? '', + mask: formatter.mask, + ); } }) .toList(), diff --git a/packages/stac/lib/src/parsers/widgets/stac_selectable_text/stac_selectable_text_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_selectable_text/stac_selectable_text_parser.dart index 2f18a7b81..8a8cf4da0 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_selectable_text/stac_selectable_text_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_selectable_text/stac_selectable_text_parser.dart @@ -92,6 +92,7 @@ class StacSelectableTextParser extends StacParser { textBaseline: overrideParsed.textBaseline, height: overrideParsed.height, leadingDistribution: overrideParsed.leadingDistribution, + decoration: overrideParsed.decoration, decorationColor: overrideParsed.decorationColor, decorationStyle: overrideParsed.decorationStyle, decorationThickness: overrideParsed.decorationThickness, diff --git a/packages/stac/lib/src/parsers/widgets/stac_text/stac_text_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_text/stac_text_parser.dart index 457c905d1..1457eccd2 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_text/stac_text_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_text/stac_text_parser.dart @@ -81,6 +81,7 @@ class StacTextParser extends StacParser { textBaseline: overrideParsed.textBaseline, height: overrideParsed.height, leadingDistribution: overrideParsed.leadingDistribution, + decoration: overrideParsed.decoration, decorationColor: overrideParsed.decorationColor, decorationStyle: overrideParsed.decorationStyle, decorationThickness: overrideParsed.decorationThickness, diff --git a/packages/stac/lib/src/parsers/widgets/stac_text_form_field/stac_text_form_field_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_text_form_field/stac_text_form_field_parser.dart index 041769458..361baf86e 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_text_form_field/stac_text_form_field_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_text_form_field/stac_text_form_field_parser.dart @@ -55,12 +55,20 @@ class _TextFormFieldWidgetState extends State<_TextFormFieldWidget> { void initState() { super.initState(); - _controller = TextEditingController(text: widget.model.initialValue); _obscureText = widget.model.obscureText ?? false; - if (widget.model.id != null) { - widget.formScope?.formData[widget.model.id!] = widget.model.initialValue; + String resolvedText = widget.model.initialValue ?? ''; + final id = widget.model.id; + final scope = widget.formScope; + if (id != null && scope != null) { + final existing = scope.formData[id]; + if (existing != null && existing.toString().trim().isNotEmpty) { + resolvedText = existing.toString(); + } + scope.formData[id] = resolvedText; } + + _controller = TextEditingController(text: resolvedText); } @override @@ -108,8 +116,10 @@ class _TextFormFieldWidgetState extends State<_TextFormFieldWidget> { decoration: widget.model.decoration?.parse(context), inputFormatters: widget.model.inputFormatters ?.map( - (inputFormatter) => - inputFormatter.type.parse.format(inputFormatter.rule ?? ""), + (inputFormatter) => inputFormatter.type.parse.format( + inputFormatter.rule ?? "", + mask: inputFormatter.mask, + ), ) .toList(), validator: (value) { diff --git a/packages/stac/lib/src/utils/input_formatters.dart b/packages/stac/lib/src/utils/input_formatters.dart index 2bc3ff15a..a93dd1740 100644 --- a/packages/stac/lib/src/utils/input_formatters.dart +++ b/packages/stac/lib/src/utils/input_formatters.dart @@ -3,9 +3,10 @@ import 'package:stac_logger/stac_logger.dart'; enum InputFormatterType { allow, - deny; + deny, + mask; - TextInputFormatter format(String rule) { + TextInputFormatter format(String rule, {String? mask}) { try { switch (this) { case InputFormatterType.allow: @@ -13,6 +14,9 @@ enum InputFormatterType { case InputFormatterType.deny: return FilteringTextInputFormatter.deny(RegExp(rule)); + + case InputFormatterType.mask: + return _StacMaskInputFormatter(mask: mask ?? '', rule: rule); } } catch (e) { Log.e(e); @@ -20,3 +24,43 @@ enum InputFormatterType { } } } + +class _StacMaskInputFormatter extends TextInputFormatter { + _StacMaskInputFormatter({required this.mask, required String rule}) + : _allowed = RegExp(rule.isEmpty ? r'.' : rule); + + final String mask; + final RegExp _allowed; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + if (mask.isEmpty) return newValue; + + final raw = newValue.text + .split('') + .where((character) => _allowed.hasMatch(character)) + .join(); + final buffer = StringBuffer(); + var rawIndex = 0; + + for (var maskIndex = 0; maskIndex < mask.length; maskIndex += 1) { + final token = mask[maskIndex]; + if (token == '#') { + if (rawIndex >= raw.length) break; + buffer.write(raw[rawIndex]); + rawIndex += 1; + } else if (rawIndex < raw.length) { + buffer.write(token); + } + } + + final text = buffer.toString(); + return TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length), + ); + } +} diff --git a/packages/stac/test/utils/input_formatters_test.dart b/packages/stac/test/utils/input_formatters_test.dart new file mode 100644 index 000000000..89017426a --- /dev/null +++ b/packages/stac/test/utils/input_formatters_test.dart @@ -0,0 +1,28 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stac/src/utils/input_formatters.dart'; + +void main() { + group('InputFormatterType.mask', () { + test('applies separators and filters disallowed characters', () { + final formatter = InputFormatterType.mask.format( + r'\d', + mask: '##/##/####', + ); + + final value = formatter.formatEditUpdate( + TextEditingValue.empty, + const TextEditingValue(text: '1a2345678'), + ); + + expect(value.text, '12/34/5678'); + expect(value.selection.baseOffset, value.text.length); + }); + + test('returns the new value when mask is empty', () { + final formatter = InputFormatterType.mask.format(r'\d'); + const value = TextEditingValue(text: '123'); + + expect(formatter.formatEditUpdate(TextEditingValue.empty, value), value); + }); + }); +} diff --git a/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.dart b/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.dart index c9780fcd2..1eb9851b7 100644 --- a/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.dart +++ b/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.dart @@ -43,6 +43,7 @@ class StacInputDecoration extends StacElement { this.icon, this.labelText, this.labelStyle, + this.floatingLabelBehavior, this.hintText, this.hintStyle, this.helperText, @@ -77,6 +78,12 @@ class StacInputDecoration extends StacElement { /// Text style for [labelText]. final StacTextStyle? labelStyle; + /// Controls when the label floats above the input. + /// + /// Supported values mirror Flutter's [FloatingLabelBehavior]: `auto`, + /// `always`, and `never`. + final String? floatingLabelBehavior; + /// Optional placeholder text. final String? hintText; diff --git a/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.g.dart b/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.g.dart index 2e732b1cd..1283999d5 100644 --- a/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.g.dart +++ b/packages/stac_core/lib/foundation/decoration/stac_input_decoration/stac_input_decoration.g.dart @@ -16,6 +16,7 @@ StacInputDecoration _$StacInputDecorationFromJson( labelStyle: json['labelStyle'] == null ? null : StacTextStyle.fromJson(json['labelStyle']), + floatingLabelBehavior: json['floatingLabelBehavior'] as String?, hintText: json['hintText'] as String?, hintStyle: json['hintStyle'] == null ? null @@ -79,6 +80,7 @@ Map _$StacInputDecorationToJson( 'icon': instance.icon?.toJson(), 'labelText': instance.labelText, 'labelStyle': instance.labelStyle?.toJson(), + 'floatingLabelBehavior': instance.floatingLabelBehavior, 'hintText': instance.hintText, 'hintStyle': instance.hintStyle?.toJson(), 'helperText': instance.helperText, diff --git a/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.dart b/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.dart index 3b3a38a61..e090a645a 100644 --- a/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.dart +++ b/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.dart @@ -10,7 +10,7 @@ part 'stac_input_formatter.g.dart'; @JsonSerializable() class StacInputFormatter extends StacElement { /// Creates an input formatter with the specified type and optional rule. - const StacInputFormatter({required this.type, this.rule}); + const StacInputFormatter({required this.type, this.rule, this.mask}); /// Formatter behavior: allow or deny based on a regular expression rule. final StacInputFormatterType type; @@ -18,6 +18,12 @@ class StacInputFormatter extends StacElement { /// Regular expression string used by the formatter. final String? rule; + /// Input mask used when [type] is [StacInputFormatterType.mask]. + /// + /// `#` positions consume characters that match [rule]. All other characters + /// are inserted as fixed separators. + final String? mask; + /// Creates a [StacInputFormatter] from a JSON map. factory StacInputFormatter.fromJson(Map json) => _$StacInputFormatterFromJson(json); @@ -37,4 +43,7 @@ enum StacInputFormatterType { /// Deny characters that match the provided regex [rule]. deny, + + /// Apply a simple input mask. `#` consumes allowed input characters. + mask, } diff --git a/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.g.dart b/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.g.dart index b0460a02c..bb80286c3 100644 --- a/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.g.dart +++ b/packages/stac_core/lib/foundation/forms/stac_input_formatter/stac_input_formatter.g.dart @@ -10,15 +10,18 @@ StacInputFormatter _$StacInputFormatterFromJson(Map json) => StacInputFormatter( type: $enumDecode(_$StacInputFormatterTypeEnumMap, json['type']), rule: json['rule'] as String?, + mask: json['mask'] as String?, ); Map _$StacInputFormatterToJson(StacInputFormatter instance) => { 'type': _$StacInputFormatterTypeEnumMap[instance.type]!, 'rule': instance.rule, + 'mask': instance.mask, }; const _$StacInputFormatterTypeEnumMap = { StacInputFormatterType.allow: 'allow', StacInputFormatterType.deny: 'deny', + StacInputFormatterType.mask: 'mask', }; diff --git a/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.dart b/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.dart index 6c7c990ee..b9a11a360 100644 --- a/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.dart +++ b/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.dart @@ -199,6 +199,7 @@ abstract class StacTextStyle implements StacElement { StacTextBaseline? textBaseline, double? height, StacTextLeadingDistribution? leadingDistribution, + StacTextDecorationLine? decoration, StacColor? decorationColor, StacTextDecorationStyle? decorationStyle, double? decorationThickness, @@ -220,6 +221,7 @@ abstract class StacTextStyle implements StacElement { textBaseline: textBaseline, height: height, leadingDistribution: leadingDistribution, + decoration: decoration, decorationColor: decorationColor, decorationStyle: decorationStyle, decorationThickness: decorationThickness, @@ -349,6 +351,7 @@ class StacCustomTextStyle extends StacTextStyle { this.textBaseline, this.height, this.leadingDistribution, + this.decoration, this.decorationColor, this.decorationStyle, this.decorationThickness, @@ -414,6 +417,11 @@ class StacCustomTextStyle extends StacTextStyle { /// Type: [StacTextLeadingDistribution] StacTextLeadingDistribution? leadingDistribution; + /// Drawn line on the text (underline, strikethrough, etc.). + /// + /// Type: [StacTextDecorationLine] + StacTextDecorationLine? decoration; + /// Color for text decorations (underline, overline, etc.). /// /// Type: [StacColor] @@ -475,6 +483,7 @@ class StacCustomTextStyle extends StacTextStyle { StacTextBaseline? textBaseline, double? height, StacTextLeadingDistribution? leadingDistribution, + StacTextDecorationLine? decoration, StacColor? decorationColor, StacTextDecorationStyle? decorationStyle, double? decorationThickness, @@ -496,6 +505,7 @@ class StacCustomTextStyle extends StacTextStyle { textBaseline: textBaseline ?? this.textBaseline, height: height ?? this.height, leadingDistribution: leadingDistribution ?? this.leadingDistribution, + decoration: decoration ?? this.decoration, decorationColor: decorationColor ?? this.decorationColor, decorationStyle: decorationStyle ?? this.decorationStyle, decorationThickness: decorationThickness ?? this.decorationThickness, diff --git a/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.g.dart b/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.g.dart index dc94ce994..5fb06c89b 100644 --- a/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.g.dart +++ b/packages/stac_core/lib/foundation/text/stac_text_style/stac_text_style.g.dart @@ -26,6 +26,10 @@ StacCustomTextStyle _$StacCustomTextStyleFromJson( _$StacTextLeadingDistributionEnumMap, json['leadingDistribution'], ), + decoration: $enumDecodeNullable( + _$StacTextDecorationLineEnumMap, + json['decoration'], + ), decorationColor: json['decorationColor'] as String?, decorationStyle: $enumDecodeNullable( _$StacTextDecorationStyleEnumMap, @@ -57,6 +61,7 @@ Map _$StacCustomTextStyleToJson( 'height': instance.height, 'leadingDistribution': _$StacTextLeadingDistributionEnumMap[instance.leadingDistribution], + 'decoration': _$StacTextDecorationLineEnumMap[instance.decoration], 'decorationColor': instance.decorationColor, 'decorationStyle': _$StacTextDecorationStyleEnumMap[instance.decorationStyle], 'decorationThickness': instance.decorationThickness, @@ -96,6 +101,13 @@ const _$StacTextLeadingDistributionEnumMap = { StacTextLeadingDistribution.even: 'even', }; +const _$StacTextDecorationLineEnumMap = { + StacTextDecorationLine.none: 'none', + StacTextDecorationLine.underline: 'underline', + StacTextDecorationLine.overline: 'overline', + StacTextDecorationLine.lineThrough: 'lineThrough', +}; + const _$StacTextDecorationStyleEnumMap = { StacTextDecorationStyle.solid: 'solid', StacTextDecorationStyle.double: 'double', diff --git a/packages/stac_core/lib/foundation/text/stac_text_types.dart b/packages/stac_core/lib/foundation/text/stac_text_types.dart index 50be7ce62..88c7845fa 100644 --- a/packages/stac_core/lib/foundation/text/stac_text_types.dart +++ b/packages/stac_core/lib/foundation/text/stac_text_types.dart @@ -132,3 +132,18 @@ enum StacTextDecorationStyle { /// Wavy line decoration. wavy, } + +/// Line painted on text (maps to Flutter [TextDecoration]). +enum StacTextDecorationLine { + /// No line decoration. + none, + + /// Underline. + underline, + + /// Overline. + overline, + + /// Strikethrough. + lineThrough, +} diff --git a/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.dart b/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.dart index 1975cc557..f019ce3dd 100644 --- a/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.dart +++ b/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.dart @@ -1,5 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stac_core/core/core.dart'; +import 'package:stac_core/foundation/borders/stac_shape_border/stac_shape_border.dart'; +import 'package:stac_core/foundation/geometry/stac_box_constraints/stac_box_constraints.dart'; import 'package:stac_core/foundation/geometry/stac_edge_insets/stac_edge_insets.dart'; import 'package:stac_core/foundation/text/stac_text_style/stac_text_style.dart'; @@ -49,8 +51,10 @@ class StacFloatingActionButtonThemeData implements StacElement { this.hoverElevation, this.disabledElevation, this.highlightElevation, + this.shape, this.enableFeedback, this.iconSize, + this.sizeConstraints, this.extendedIconLabelSpacing, this.extendedPadding, this.extendedTextStyle, @@ -86,12 +90,18 @@ class StacFloatingActionButtonThemeData implements StacElement { /// The z-coordinate at which to place this floating action button when it's highlighted. final double? highlightElevation; + /// The shape of this button's [Material]. + final StacShapeBorder? shape; + /// Whether detected gestures should provide acoustic and/or haptic feedback. final bool? enableFeedback; /// The size of the floating action button's icon. final double? iconSize; + /// Layout constraints for the regular-size floating action button. + final StacBoxConstraints? sizeConstraints; + /// The spacing between the icon and label in an extended floating action button. final double? extendedIconLabelSpacing; diff --git a/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.g.dart b/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.g.dart index c785a274a..3b389f683 100644 --- a/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.g.dart +++ b/packages/stac_core/lib/foundation/theme/stac_floating_action_button_theme_data/stac_floating_action_button_theme_data.g.dart @@ -19,8 +19,16 @@ StacFloatingActionButtonThemeData _$StacFloatingActionButtonThemeDataFromJson( hoverElevation: (json['hoverElevation'] as num?)?.toDouble(), disabledElevation: (json['disabledElevation'] as num?)?.toDouble(), highlightElevation: (json['highlightElevation'] as num?)?.toDouble(), + shape: json['shape'] == null + ? null + : StacShapeBorder.fromJson(json['shape'] as Map), enableFeedback: json['enableFeedback'] as bool?, iconSize: (json['iconSize'] as num?)?.toDouble(), + sizeConstraints: json['sizeConstraints'] == null + ? null + : StacBoxConstraints.fromJson( + json['sizeConstraints'] as Map, + ), extendedIconLabelSpacing: (json['extendedIconLabelSpacing'] as num?) ?.toDouble(), extendedPadding: json['extendedPadding'] == null @@ -44,8 +52,10 @@ Map _$StacFloatingActionButtonThemeDataToJson( 'hoverElevation': instance.hoverElevation, 'disabledElevation': instance.disabledElevation, 'highlightElevation': instance.highlightElevation, + 'shape': instance.shape?.toJson(), 'enableFeedback': instance.enableFeedback, 'iconSize': instance.iconSize, + 'sizeConstraints': instance.sizeConstraints?.toJson(), 'extendedIconLabelSpacing': instance.extendedIconLabelSpacing, 'extendedPadding': instance.extendedPadding?.toJson(), 'extendedTextStyle': instance.extendedTextStyle?.toJson(), From 97a29d070dc45dc01e3048cbcda380fb4616f01c Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 12 May 2026 18:47:33 +0530 Subject: [PATCH 2/3] Fix mask input formatter rule matching --- packages/stac/lib/src/utils/input_formatters.dart | 2 +- .../stac/test/utils/input_formatters_test.dart | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/stac/lib/src/utils/input_formatters.dart b/packages/stac/lib/src/utils/input_formatters.dart index a93dd1740..65e4dc8d2 100644 --- a/packages/stac/lib/src/utils/input_formatters.dart +++ b/packages/stac/lib/src/utils/input_formatters.dart @@ -27,7 +27,7 @@ enum InputFormatterType { class _StacMaskInputFormatter extends TextInputFormatter { _StacMaskInputFormatter({required this.mask, required String rule}) - : _allowed = RegExp(rule.isEmpty ? r'.' : rule); + : _allowed = RegExp('^${rule.isEmpty ? r'.' : rule}\$'); final String mask; final RegExp _allowed; diff --git a/packages/stac/test/utils/input_formatters_test.dart b/packages/stac/test/utils/input_formatters_test.dart index 89017426a..d96cb87ea 100644 --- a/packages/stac/test/utils/input_formatters_test.dart +++ b/packages/stac/test/utils/input_formatters_test.dart @@ -18,6 +18,20 @@ void main() { expect(value.selection.baseOffset, value.text.length); }); + test('anchors rules so empty-substring matches are rejected', () { + final formatter = InputFormatterType.mask.format( + r'\d*', + mask: '##/##', + ); + + final value = formatter.formatEditUpdate( + TextEditingValue.empty, + const TextEditingValue(text: 'a1b2'), + ); + + expect(value.text, '12'); + }); + test('returns the new value when mask is empty', () { final formatter = InputFormatterType.mask.format(r'\d'); const value = TextEditingValue(text: '123'); From af8b7e579cd98c4b3ae709fde41e277c3a8531d9 Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 12 May 2026 18:55:45 +0530 Subject: [PATCH 3/3] Refactor input formatter test for improved readability --- packages/stac/test/utils/input_formatters_test.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/stac/test/utils/input_formatters_test.dart b/packages/stac/test/utils/input_formatters_test.dart index d96cb87ea..4f24c122c 100644 --- a/packages/stac/test/utils/input_formatters_test.dart +++ b/packages/stac/test/utils/input_formatters_test.dart @@ -19,10 +19,7 @@ void main() { }); test('anchors rules so empty-substring matches are rejected', () { - final formatter = InputFormatterType.mask.format( - r'\d*', - mask: '##/##', - ); + final formatter = InputFormatterType.mask.format(r'\d*', mask: '##/##'); final value = formatter.formatEditUpdate( TextEditingValue.empty,