From fb7ee1a18ff7e1602f72e6e14b557e48faeee14c Mon Sep 17 00:00:00 2001 From: TheEthicalBoy <98978078+ndonkoHenri@users.noreply.github.com> Date: Thu, 9 May 2024 21:07:07 +0200 Subject: [PATCH] `AutoFillGroup` Control (#3047) * AutoFillHint: initial commit * Mouse utils: lowercase before parsing * AutoFillGroup: initial commit * rename AutoFill to Autofill, remove opacity+visible+disabled * content property is required * rename AutoFillHint to AutofillHint * Bug-fix: ExpansionPanel.bgcolor regression --- .../flet/lib/src/controls/autofill_group.dart | 41 ++++ .../flet/lib/src/controls/create_control.dart | 9 + .../lib/src/controls/expansion_panel.dart | 2 +- .../flet/lib/src/controls/radio_group.dart | 3 +- packages/flet/lib/src/controls/textfield.dart | 2 + packages/flet/lib/src/utils/autofill.dart | 180 ++++++++++++++++++ packages/flet/lib/src/utils/mouse.dart | 42 ++-- .../flet-core/src/flet_core/__init__.py | 19 +- .../flet-core/src/flet_core/autofill_group.py | 137 +++++++++++++ .../flet-core/src/flet_core/radio_group.py | 8 +- .../flet-core/src/flet_core/textfield.py | 29 ++- 11 files changed, 437 insertions(+), 35 deletions(-) create mode 100644 packages/flet/lib/src/controls/autofill_group.dart create mode 100644 packages/flet/lib/src/utils/autofill.dart create mode 100644 sdk/python/packages/flet-core/src/flet_core/autofill_group.py diff --git a/packages/flet/lib/src/controls/autofill_group.dart b/packages/flet/lib/src/controls/autofill_group.dart new file mode 100644 index 000000000..bb925a0e0 --- /dev/null +++ b/packages/flet/lib/src/controls/autofill_group.dart @@ -0,0 +1,41 @@ +import 'package:flet/src/utils/autofill.dart'; +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import 'create_control.dart'; +import 'error.dart'; + +class AutofillGroupControl extends StatelessWidget { + final Control? parent; + final Control control; + final List children; + final bool parentDisabled; + final bool? parentAdaptive; + + const AutofillGroupControl( + {super.key, + required this.parent, + required this.control, + required this.children, + required this.parentDisabled, + this.parentAdaptive}); + + @override + Widget build(BuildContext context) { + debugPrint("AutofillGroup build: ${control.id}"); + + var contentCtrls = + children.where((c) => c.name == "content" && c.isVisible); + bool disabled = control.isDisabled || parentDisabled; + + if (contentCtrls.isEmpty) { + return const ErrorControl("AutofillGroup control has no content."); + } + + return AutofillGroup( + onDisposeAction: parseAutofillContextAction( + control.attrString("disposeAction"), AutofillContextAction.commit)!, + child: createControl(control, contentCtrls.first.id, disabled, + parentAdaptive: parentAdaptive)); + } +} diff --git a/packages/flet/lib/src/controls/create_control.dart b/packages/flet/lib/src/controls/create_control.dart index b57fd7bc9..b45738263 100644 --- a/packages/flet/lib/src/controls/create_control.dart +++ b/packages/flet/lib/src/controls/create_control.dart @@ -17,6 +17,7 @@ import '../utils/transforms.dart'; import 'alert_dialog.dart'; import 'animated_switcher.dart'; import 'auto_complete.dart'; +import 'autofill_group.dart'; import 'badge.dart'; import 'banner.dart'; import 'barchart.dart'; @@ -803,6 +804,14 @@ Widget createWidget( parentDisabled: parentDisabled, parentAdaptive: parentAdaptive, backend: backend); + case "autofillgroup": + return AutofillGroupControl( + key: key, + parent: parent, + control: controlView.control, + children: controlView.children, + parentDisabled: parentDisabled, + parentAdaptive: parentAdaptive); case "cupertinoradio": return CupertinoRadioControl( key: key, diff --git a/packages/flet/lib/src/controls/expansion_panel.dart b/packages/flet/lib/src/controls/expansion_panel.dart index 1942ebb40..3d8bc94f1 100644 --- a/packages/flet/lib/src/controls/expansion_panel.dart +++ b/packages/flet/lib/src/controls/expansion_panel.dart @@ -81,7 +81,7 @@ class _ExpansionPanelListControlState extends State var isExpanded = panelView.control.attrBool("expanded", false)!; var canTapHeader = panelView.control.attrBool("canTapHeader", false)!; - var bgColor = widget.control.attrColor("bgColor", context); + var bgColor = panelView.control.attrColor("bgColor", context); return ExpansionPanel( backgroundColor: bgColor, diff --git a/packages/flet/lib/src/controls/radio_group.dart b/packages/flet/lib/src/controls/radio_group.dart index 4f79421cb..aceb805c7 100644 --- a/packages/flet/lib/src/controls/radio_group.dart +++ b/packages/flet/lib/src/controls/radio_group.dart @@ -28,8 +28,7 @@ class RadioGroupControl extends StatelessWidget { bool disabled = control.isDisabled || parentDisabled; if (contentCtrls.isEmpty) { - return const ErrorControl( - "RadioGroup control does not have any content."); + return const ErrorControl("RadioGroup control has no content."); } return createControl(control, contentCtrls.first.id, disabled, diff --git a/packages/flet/lib/src/controls/textfield.dart b/packages/flet/lib/src/controls/textfield.dart index 182099798..7dba35af6 100644 --- a/packages/flet/lib/src/controls/textfield.dart +++ b/packages/flet/lib/src/controls/textfield.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import '../flet_control_backend.dart'; import '../models/control.dart'; +import '../utils/autofill.dart'; import '../utils/borders.dart'; import '../utils/form_field.dart'; import '../utils/text.dart'; @@ -264,6 +265,7 @@ class _TextFieldControlState extends State obscureText: password && !_revealPassword, controller: _controller, focusNode: focusNode, + autofillHints: parseAutofillHints(widget.control, "autofillHints"), onChanged: (String value) { //debugPrint(value); _value = value; diff --git a/packages/flet/lib/src/utils/autofill.dart b/packages/flet/lib/src/utils/autofill.dart new file mode 100644 index 000000000..32fe132d7 --- /dev/null +++ b/packages/flet/lib/src/utils/autofill.dart @@ -0,0 +1,180 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import '../models/control.dart'; + +List? parseAutofillHints(Control control, String propName) { + var v = control.attrString(propName, null); + if (v == null) { + return null; + } + + final j1 = json.decode(v); + return autofillHintsFromJson(j1); +} + +List autofillHintsFromJson(dynamic json) { + List hints = []; + if (json is List) { + hints = json + .map((e) => autofillHintFromString(e.toString())) + .whereType() + .toList(); + } else if (json is String) { + hints = [autofillHintFromString(json)].whereType().toList(); + } + + return hints; +} + +String? autofillHintFromString(String? hint, [String? defaultAutoFillHint]) { + switch (hint?.toLowerCase()) { + case 'addresscity': + return AutofillHints.addressCity; + case 'addresscityandstate': + return AutofillHints.addressCityAndState; + case 'addressstate': + return AutofillHints.addressState; + case 'birthday': + return AutofillHints.birthday; + case 'birthdayday': + return AutofillHints.birthdayDay; + case 'birthdayMonth': + return AutofillHints.birthdayMonth; + case 'birthdayyear': + return AutofillHints.birthdayYear; + case 'countrycode': + return AutofillHints.countryCode; + case 'countryname': + return AutofillHints.countryName; + case 'creditcardexpirationdate': + return AutofillHints.creditCardExpirationDate; + case 'creditcardexpirationday': + return AutofillHints.creditCardExpirationDay; + case 'creditcardexpirationmonth': + return AutofillHints.creditCardExpirationMonth; + case 'creditcardexpirationyear': + return AutofillHints.creditCardExpirationYear; + case 'creditcardfamilyname': + return AutofillHints.creditCardFamilyName; + case 'creditcardgivenname': + return AutofillHints.creditCardGivenName; + case 'creditcardmiddlename': + return AutofillHints.creditCardMiddleName; + case 'creditcardname': + return AutofillHints.creditCardName; + case 'creditcardnumber': + return AutofillHints.creditCardNumber; + case 'creditcardsecuritycode': + return AutofillHints.creditCardSecurityCode; + case 'creditcardtype': + return AutofillHints.creditCardType; + case 'email': + return AutofillHints.email; + case 'familyname': + return AutofillHints.familyName; + case 'fullstreetaddress': + return AutofillHints.fullStreetAddress; + case 'gender': + return AutofillHints.gender; + case 'givenname': + return AutofillHints.givenName; + case 'impp': + return AutofillHints.impp; + case 'jobtitle': + return AutofillHints.jobTitle; + case 'language': + return AutofillHints.language; + case 'location': + return AutofillHints.location; + case 'middleinitial': + return AutofillHints.middleInitial; + case 'middlename': + return AutofillHints.middleName; + case 'name': + return AutofillHints.name; + case 'nameprefix': + return AutofillHints.namePrefix; + case 'namesuffix': + return AutofillHints.nameSuffix; + case 'newpassword': + return AutofillHints.newPassword; + case 'newusername': + return AutofillHints.newUsername; + case 'nickname': + return AutofillHints.nickname; + case 'onetimecode': + return AutofillHints.oneTimeCode; + case 'organizationname': + return AutofillHints.organizationName; + case 'password': + return AutofillHints.password; + case 'photo': + return AutofillHints.photo; + case 'postaladdress': + return AutofillHints.postalAddress; + case 'postaladdressextended': + return AutofillHints.postalAddressExtended; + case 'postaladdressextendedpostalcode': + return AutofillHints.postalAddressExtendedPostalCode; + case 'postalcode': + return AutofillHints.postalCode; + case 'streetaddresslevel1': + return AutofillHints.streetAddressLevel1; + case 'streetaddresslevel2': + return AutofillHints.streetAddressLevel2; + case 'streetaddresslevel3': + return AutofillHints.streetAddressLevel3; + case 'streetaddresslevel4': + return AutofillHints.streetAddressLevel4; + case 'streetaddressline1': + return AutofillHints.streetAddressLine1; + case 'streetaddressline2': + return AutofillHints.streetAddressLine2; + case 'streetaddressline3': + return AutofillHints.streetAddressLine3; + case 'sublocality': + return AutofillHints.sublocality; + case 'telephonenumber': + return AutofillHints.telephoneNumber; + case 'telephonenumberareacode': + return AutofillHints.telephoneNumberAreaCode; + case 'telephonenumbercountrycode': + return AutofillHints.telephoneNumberCountryCode; + case 'telephonenumberdevice': + return AutofillHints.telephoneNumberDevice; + case 'telephonenumberextension': + return AutofillHints.telephoneNumberExtension; + case 'telephonenumberlocal': + return AutofillHints.telephoneNumberLocal; + case 'telephonenumberlocalprefix': + return AutofillHints.telephoneNumberLocalPrefix; + case 'telephonenumberlocalsuffix': + return AutofillHints.telephoneNumberLocalSuffix; + case 'telephonenumbernational': + return AutofillHints.telephoneNumberNational; + case 'transactionamount': + return AutofillHints.transactionAmount; + case 'transactioncurrency': + return AutofillHints.transactionCurrency; + case 'url': + return AutofillHints.url; + case 'username': + return AutofillHints.username; + default: + return defaultAutoFillHint; + } +} + +AutofillContextAction? parseAutofillContextAction(String? action, + [AutofillContextAction? defaultAction]) { + switch (action?.toLowerCase()) { + case 'commit': + return AutofillContextAction.commit; + case 'cancel': + return AutofillContextAction.cancel; + default: + return defaultAction; + } +} diff --git a/packages/flet/lib/src/utils/mouse.dart b/packages/flet/lib/src/utils/mouse.dart index 80d1dbcf0..d482f2cf4 100644 --- a/packages/flet/lib/src/utils/mouse.dart +++ b/packages/flet/lib/src/utils/mouse.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; MouseCursor? parseMouseCursor(String? cursor, [MouseCursor? defaultMouseCursor = MouseCursor.defer]) { - switch (cursor) { + switch (cursor?.toLowerCase()) { case "alias": return SystemMouseCursors.alias; - case "allScroll": + case "allscroll": return SystemMouseCursors.allScroll; case "basic": return SystemMouseCursors.basic; @@ -13,7 +13,7 @@ MouseCursor? parseMouseCursor(String? cursor, return SystemMouseCursors.cell; case "click": return SystemMouseCursors.click; - case "contextMenu": + case "contextmenu": return SystemMouseCursors.contextMenu; case "copy": return SystemMouseCursors.copy; @@ -29,7 +29,7 @@ MouseCursor? parseMouseCursor(String? cursor, return SystemMouseCursors.help; case "move": return SystemMouseCursors.move; - case "noDrop": + case "nodrop": return SystemMouseCursors.noDrop; case "none": return SystemMouseCursors.none; @@ -37,43 +37,43 @@ MouseCursor? parseMouseCursor(String? cursor, return SystemMouseCursors.precise; case "progress": return SystemMouseCursors.progress; - case "resizeColumn": + case "resizecolumn": return SystemMouseCursors.resizeColumn; - case "resizeDown": + case "resizedown": return SystemMouseCursors.resizeDown; - case "resizeDownLeft": + case "resizedownleft": return SystemMouseCursors.resizeDownLeft; - case "resizeDownRight": + case "resizedownright": return SystemMouseCursors.resizeDownRight; - case "resizeLeft": + case "resizeleft": return SystemMouseCursors.resizeLeft; - case "resizeLeftRight": + case "resizeleftright": return SystemMouseCursors.resizeLeftRight; - case "resizeRight": + case "resizeright": return SystemMouseCursors.resizeRight; - case "resizeRow": + case "resizerow": return SystemMouseCursors.resizeRow; - case "resizeUp": + case "resizeup": return SystemMouseCursors.resizeUp; - case "resizeUpDown": + case "resizeupdown": return SystemMouseCursors.resizeUpDown; - case "resizeUpLeft": + case "resizeupleft": return SystemMouseCursors.resizeUpLeft; - case "resizeUpLeftDownRight": + case "resizeupleftdownright": return SystemMouseCursors.resizeUpLeftDownRight; - case "resizeUpRight": + case "resizeupright": return SystemMouseCursors.resizeUpRight; - case "resizeUpRightDownLeft": + case "resizeuprightdownleft": return SystemMouseCursors.resizeUpRightDownLeft; case "text": return SystemMouseCursors.text; - case "verticalText": + case "verticaltext": return SystemMouseCursors.verticalText; case "wait": return SystemMouseCursors.wait; - case "zoomIn": + case "zoomin": return SystemMouseCursors.zoomIn; - case "zoomOut": + case "zoomout": return SystemMouseCursors.zoomOut; default: return defaultMouseCursor; diff --git a/sdk/python/packages/flet-core/src/flet_core/__init__.py b/sdk/python/packages/flet-core/src/flet_core/__init__.py index 137e0048f..e3d480131 100644 --- a/sdk/python/packages/flet-core/src/flet_core/__init__.py +++ b/sdk/python/packages/flet-core/src/flet_core/__init__.py @@ -23,7 +23,11 @@ from flet_core.app_bar import AppBar from flet_core.audio import Audio from flet_core.audio_recorder import AudioEncoder, AudioRecorder -from flet_core.auto_complete import AutoComplete, AutoCompleteSuggestion +from flet_core.autofill_group import ( + AutofillGroup, + AutofillGroupDisposeAction, + AutofillHint, +) from flet_core.badge import Badge from flet_core.banner import Banner from flet_core.blur import Blur, BlurTileMode @@ -104,7 +108,12 @@ DataRow, DataTable, ) -from flet_core.date_picker import DatePicker, DatePickerEntryMode, DatePickerMode, DatePickerEntryModeChangeEvent +from flet_core.date_picker import ( + DatePicker, + DatePickerEntryMode, + DatePickerMode, + DatePickerEntryModeChangeEvent, +) from flet_core.dismissible import ( Dismissible, DismissibleDismissEvent, @@ -283,7 +292,11 @@ TimePickerTheme, TooltipTheme, ) -from flet_core.time_picker import TimePicker, TimePickerEntryMode, TimePickerEntryModeChangeEvent +from flet_core.time_picker import ( + TimePicker, + TimePickerEntryMode, + TimePickerEntryModeChangeEvent, +) from flet_core.tooltip import Tooltip from flet_core.transform import Offset, Rotate, Scale from flet_core.transparent_pointer import TransparentPointer diff --git a/sdk/python/packages/flet-core/src/flet_core/autofill_group.py b/sdk/python/packages/flet-core/src/flet_core/autofill_group.py new file mode 100644 index 000000000..cf4a60048 --- /dev/null +++ b/sdk/python/packages/flet-core/src/flet_core/autofill_group.py @@ -0,0 +1,137 @@ +from enum import Enum +from typing import Any, Optional + +from flet_core.control import Control +from flet_core.ref import Ref + + +class AutofillHint(Enum): + ADDRESS_CITY = "addressCity" + ADDRESS_CITY_AND_STATE = "addressCityAndState" + ADDRESS_STATE = "addressState" + BIRTHDAY = "birthday" + BIRTHDAY_DAY = "birthdayDay" + BIRTHDAY_MONTH = "birthdayMonth" + BIRTHDAY_YEAR = "birthdayYear" + COUNTRY_CODE = "countryCode" + COUNTRY_NAME = "countryName" + CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate" + CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay" + CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth" + CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear" + CREDIT_CARD_FAMILY_NAME = "creditCardFamilyName" + CREDIT_CARD_GIVEN_NAME = "creditCardGivenName" + CREDIT_CARD_MIDDLE_NAME = "creditCardMiddleName" + CREDIT_CARD_NAME = "creditCardName" + CREDIT_CARD_NUMBER = "creditCardNumber" + CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode" + CREDIT_CARD_TYPE = "creditCardType" + EMAIL = "email" + FAMILY_NAME = "familyName" + FULL_STREET_ADDRESS = "fullStreetAddress" + GENDER = "gender" + GIVEN_NAME = "givenName" + IMPP = "impp" + JOB_TITLE = "jobTitle" + LANGUAGE = "language" + LOCATION = "location" + MIDDLE_INITIAL = "middleInitial" + MIDDLE_NAME = "middleName" + NAME = "name" + NAME_PREFIX = "namePrefix" + NAME_SUFFIX = "nameSuffix" + NEW_PASSWORD = "newPassword" + NEW_USERNAME = "newUsername" + NICKNAME = "nickname" + ONE_TIME_CODE = "oneTimeCode" + ORGANIZATION_NAME = "organizationName" + PASSWORD = "password" + PHOTO = "photo" + POSTAL_ADDRESS = "postalAddress" + POSTAL_ADDRESS_EXTENDED = "postalAddressExtended" + POSTAL_ADDRESS_EXTENDED_POSTAL_CODE = "postalAddressExtendedPostalCode" + POSTAL_CODE = "postalCode" + STREET_ADDRESS_LEVEL1 = "streetAddressLevel1" + STREET_ADDRESS_LEVEL2 = "streetAddressLevel2" + STREET_ADDRESS_LEVEL3 = "streetAddressLevel3" + STREET_ADDRESS_LEVEL4 = "streetAddressLevel4" + STREET_ADDRESS_LINE1 = "streetAddressLine1" + STREET_ADDRESS_LINE2 = "streetAddressLine2" + STREET_ADDRESS_LINE3 = "streetAddressLine3" + SUB_LOCALITY = "sublocality" + TELEPHONE_NUMBER = "telephoneNumber" + TELEPHONE_NUMBER_AREA_CODE = "telephoneNumberAreaCode" + TELEPHONE_NUMBER_COUNTRY_CODE = "telephoneNumberCountryCode" + TELEPHONE_NUMBER_DEVICE = "telephoneNumberDevice" + TELEPHONE_NUMBER_EXTENSION = "telephoneNumberExtension" + TELEPHONE_NUMBER_LOCAL = "telephoneNumberLocal" + TELEPHONE_NUMBER_LOCAL_PREFIX = "telephoneNumberLocalPrefix" + TELEPHONE_NUMBER_LOCAL_SUFFIX = "telephoneNumberLocalSuffix" + TELEPHONE_NUMBER_NATIONAL = "telephoneNumberNational" + TRANSACTION_AMOUNT = "transactionAmount" + TRANSACTION_CURRENCY = "transactionCurrency" + URL = "url" + USERNAME = "username" + + +class AutofillGroupDisposeAction(Enum): + COMMIT = "commit" + CANCEL = "cancel" + + +class AutofillGroup(Control): + """ + This control is used to group autofill controls together. + + ----- + + Online docs: https://flet.dev/docs/controls/autofillgroup + """ + + def __init__( + self, + content: Control = None, + dispose_action: Optional[AutofillGroupDisposeAction] = None, + # + # Control + # + ref: Optional[Ref] = None, + data: Any = None, + ): + + Control.__init__( + self, + ref=ref, + data=data, + ) + + self.content = content + self.dispose_action = dispose_action + + def _get_control_name(self): + return "autofillgroup" + + def _get_children(self): + if isinstance(self.__content, Control): + self.__content._set_attr_internal("n", "content") + return [self.__content] + return [] + + # content + @property + def content(self) -> Control: + return self.__content + + @content.setter + def content(self, value: Control): + self.__content = value + + # dispose_action + @property + def dispose_action(self) -> Optional[AutofillGroupDisposeAction]: + return self.__dispose_action + + @dispose_action.setter + def dispose_action(self, value: Optional[AutofillGroupDisposeAction]): + self.__dispose_action = value + self._set_enum_attr("disposeAction", value, AutofillGroupDisposeAction) diff --git a/sdk/python/packages/flet-core/src/flet_core/radio_group.py b/sdk/python/packages/flet-core/src/flet_core/radio_group.py index 055d7d3d0..a03c39225 100644 --- a/sdk/python/packages/flet-core/src/flet_core/radio_group.py +++ b/sdk/python/packages/flet-core/src/flet_core/radio_group.py @@ -66,10 +66,10 @@ def _get_control_name(self): return "radiogroup" def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] + if isinstance(self.__content, Control): + self.__content._set_attr_internal("n", "content") + return [self.__content] + return [] # value @property diff --git a/sdk/python/packages/flet-core/src/flet_core/textfield.py b/sdk/python/packages/flet-core/src/flet_core/textfield.py index ae01ddfd1..debaa8c58 100644 --- a/sdk/python/packages/flet-core/src/flet_core/textfield.py +++ b/sdk/python/packages/flet-core/src/flet_core/textfield.py @@ -2,9 +2,10 @@ import time from dataclasses import field from enum import Enum -from typing import Any, Optional, Union +from typing import Any, Optional, Union, List from flet_core.adaptive_control import AdaptiveControl +from flet_core.autofill_group import AutofillHint from flet_core.control import Control, OptionalNumber from flet_core.form_field_control import FormFieldControl, InputBorder from flet_core.ref import Ref @@ -121,6 +122,7 @@ def __init__( cursor_radius: OptionalNumber = None, selection_color: Optional[str] = None, input_filter: Optional[InputFilter] = None, + autofill_hints: Union[None, AutofillHint, List[AutofillHint]] = None, on_change=None, on_submit=None, on_focus=None, @@ -289,6 +291,7 @@ def __init__( self.cursor_radius = cursor_radius self.selection_color = selection_color self.input_filter = input_filter + self.autofill_hints = autofill_hints self.on_change = on_change self.on_submit = on_submit self.on_focus = on_focus @@ -300,6 +303,7 @@ def _get_control_name(self): def before_update(self): super().before_update() self._set_attr_json("inputFilter", self.__input_filter) + self._set_attr_json("autofillHints", self.__autofill_hints) if ( ( self.bgcolor is not None @@ -349,9 +353,7 @@ def text_align(self) -> Optional[TextAlign]: @text_align.setter def text_align(self, value: Optional[TextAlign]): self.__text_align = value - self._set_attr( - "textAlign", value.value if isinstance(value, TextAlign) else value - ) + self._set_enum_attr("textAlign", value, TextAlign) # multiline @property @@ -543,6 +545,25 @@ def input_filter(self) -> Optional[InputFilter]: def input_filter(self, value: Optional[InputFilter]): self.__input_filter = value + # autofill_hints + @property + def autofill_hints(self) -> Union[None, AutofillHint, List[AutofillHint]]: + return self.__autofill_hints + + @autofill_hints.setter + def autofill_hints(self, value: Union[None, AutofillHint, List[AutofillHint]]): + if value is not None: + if isinstance(value, List): + value = list( + map( + lambda x: x.value if isinstance(x, AutofillHint) else str(x), + value, + ) + ) + elif isinstance(value, AutofillHint): + value = value.value + self.__autofill_hints = value + # on_change @property def on_change(self):