fastlifeweb 0.27.0__py3-none-any.whl → 0.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. CHANGELOG.md +11 -0
  2. fastlife/__init__.py +2 -1
  3. fastlife/adapters/jinjax/renderer.py +8 -0
  4. fastlife/adapters/jinjax/widgets/union.py +1 -1
  5. fastlife/adapters/xcomponent/__init__.py +1 -0
  6. fastlife/adapters/xcomponent/catalog.py +11 -0
  7. fastlife/adapters/xcomponent/html/__init__.py +7 -0
  8. fastlife/adapters/xcomponent/html/collapsible.py +76 -0
  9. fastlife/adapters/xcomponent/html/form.py +437 -0
  10. fastlife/adapters/xcomponent/html/nav.py +60 -0
  11. fastlife/adapters/xcomponent/html/table.py +130 -0
  12. fastlife/adapters/xcomponent/html/text.py +30 -0
  13. fastlife/adapters/xcomponent/html/title.py +145 -0
  14. fastlife/adapters/xcomponent/icons/__init__.py +0 -0
  15. fastlife/adapters/xcomponent/icons/icons.py +93 -0
  16. fastlife/adapters/xcomponent/pydantic_form/__init__.py +0 -0
  17. fastlife/adapters/xcomponent/pydantic_form/components.py +121 -0
  18. fastlife/adapters/xcomponent/pydantic_form/widget_factory/__init__.py +1 -0
  19. fastlife/adapters/xcomponent/pydantic_form/widget_factory/base.py +40 -0
  20. fastlife/adapters/xcomponent/pydantic_form/widget_factory/bool_builder.py +45 -0
  21. fastlife/adapters/xcomponent/pydantic_form/widget_factory/emailstr_builder.py +50 -0
  22. fastlife/adapters/xcomponent/pydantic_form/widget_factory/enum_builder.py +49 -0
  23. fastlife/adapters/xcomponent/pydantic_form/widget_factory/factory.py +188 -0
  24. fastlife/adapters/xcomponent/pydantic_form/widget_factory/literal_builder.py +55 -0
  25. fastlife/adapters/xcomponent/pydantic_form/widget_factory/model_builder.py +66 -0
  26. fastlife/adapters/xcomponent/pydantic_form/widget_factory/secretstr_builder.py +48 -0
  27. fastlife/adapters/xcomponent/pydantic_form/widget_factory/sequence_builder.py +60 -0
  28. fastlife/adapters/xcomponent/pydantic_form/widget_factory/set_builder.py +85 -0
  29. fastlife/adapters/xcomponent/pydantic_form/widget_factory/simpletype_builder.py +48 -0
  30. fastlife/adapters/xcomponent/pydantic_form/widget_factory/union_builder.py +92 -0
  31. fastlife/adapters/xcomponent/pydantic_form/widgets/__init__.py +1 -0
  32. fastlife/adapters/xcomponent/pydantic_form/widgets/base.py +140 -0
  33. fastlife/adapters/xcomponent/pydantic_form/widgets/boolean.py +25 -0
  34. fastlife/adapters/xcomponent/pydantic_form/widgets/checklist.py +75 -0
  35. fastlife/adapters/xcomponent/pydantic_form/widgets/dropdown.py +72 -0
  36. fastlife/adapters/xcomponent/pydantic_form/widgets/hidden.py +25 -0
  37. fastlife/adapters/xcomponent/pydantic_form/widgets/mfa_code.py +25 -0
  38. fastlife/adapters/xcomponent/pydantic_form/widgets/model.py +49 -0
  39. fastlife/adapters/xcomponent/pydantic_form/widgets/sequence.py +74 -0
  40. fastlife/adapters/xcomponent/pydantic_form/widgets/text.py +121 -0
  41. fastlife/adapters/xcomponent/pydantic_form/widgets/union.py +81 -0
  42. fastlife/adapters/xcomponent/renderer.py +130 -0
  43. fastlife/assets/dist.css +4 -1
  44. fastlife/components/A.jinja +5 -1
  45. fastlife/config/configurator.py +7 -8
  46. fastlife/config/resources.py +9 -3
  47. fastlife/domain/model/template.py +6 -0
  48. fastlife/service/csrf.py +1 -1
  49. fastlife/service/templates.py +44 -2
  50. fastlife/template_globals.py +3 -0
  51. fastlife/views/pydantic_form.py +9 -9
  52. {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/METADATA +6 -3
  53. {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/RECORD +56 -18
  54. {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/WHEEL +1 -1
  55. {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/entry_points.txt +0 -0
  56. {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,130 @@
1
+ """Table components."""
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from xcomponent import XNode
6
+
7
+ from fastlife.adapters.xcomponent.catalog import catalog
8
+
9
+
10
+ @catalog.component
11
+ def Table(
12
+ children: XNode,
13
+ globals: Mapping[str, str],
14
+ id: str | None = None,
15
+ class_: str | None = None,
16
+ ) -> str:
17
+ """
18
+ html ``<table>`` node.
19
+
20
+ :param id: unique identifier of the element.
21
+ :param class_: css class for the node, defaults to
22
+ :attr:`fastlife.template_globals.Globals.TABLE_CLASS`.
23
+ """
24
+ return """
25
+ <table id={id} class={class_ or globals.TABLE_CLASS}>
26
+ {children}
27
+ </table>
28
+ """
29
+
30
+
31
+ @catalog.component
32
+ def Thead(
33
+ children: XNode,
34
+ globals: Mapping[str, str],
35
+ id: str | None = None,
36
+ class_: str | None = None,
37
+ ) -> str:
38
+ """
39
+ html ``<thead>`` node.
40
+
41
+ :param id: unique identifier of the element.
42
+ :param class_: css class for the node
43
+ """
44
+ return """
45
+ <thead id={id} class={class_}>
46
+ {children}
47
+ </thead>
48
+ """
49
+
50
+
51
+ @catalog.component
52
+ def Tbody(
53
+ children: XNode,
54
+ globals: Mapping[str, str],
55
+ id: str | None = None,
56
+ class_: str | None = None,
57
+ ) -> str:
58
+ """
59
+ html ``<tbody>`` node.
60
+
61
+ :param id: unique identifier of the element.
62
+ :param class_: css class for the node
63
+ """
64
+ return """
65
+ <tbody id={id} class={class_}>
66
+ {children}
67
+ </tbody>
68
+ """
69
+
70
+
71
+ @catalog.component
72
+ def Tfoot(
73
+ children: XNode,
74
+ globals: Mapping[str, str],
75
+ id: str | None = None,
76
+ class_: str | None = None,
77
+ ) -> str:
78
+ """
79
+ html ``<tfoot>`` node.
80
+
81
+ :param id: unique identifier of the element.
82
+ :param class_: css class for the node
83
+ """
84
+ return """
85
+ <tfoot id={id} class={class_}>
86
+ {children}
87
+ </tfoot>
88
+ """
89
+
90
+
91
+ @catalog.component
92
+ def Th(
93
+ children: XNode,
94
+ globals: Mapping[str, str],
95
+ id: str | None = None,
96
+ class_: str | None = None,
97
+ ) -> str:
98
+ """
99
+ html ``<th>`` node.
100
+
101
+ :param id: unique identifier of the element.
102
+ :param class_: css class for the node, defaults to
103
+ :attr:`fastlife.template_globals.Globals.TH_CLASS`.
104
+ """
105
+ return """
106
+ <th id={id} class={class_ or globals.TH_CLASS}>
107
+ {children}
108
+ </th>
109
+ """
110
+
111
+
112
+ @catalog.component
113
+ def Td(
114
+ children: XNode,
115
+ globals: Mapping[str, str],
116
+ id: str | None = None,
117
+ class_: str | None = None,
118
+ ) -> str:
119
+ """
120
+ html ``<td>`` node.
121
+
122
+ :param id: unique identifier of the element.
123
+ :param class_: css class for the node, defaults to
124
+ :attr:`fastlife.template_globals.Globals.TD_CLASS`.
125
+ """
126
+ return """
127
+ <td id={id} class={class_ or globals.TD_CLASS}>
128
+ {children}
129
+ </td>
130
+ """
@@ -0,0 +1,30 @@
1
+ """Text components."""
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from xcomponent import XNode
6
+
7
+ from fastlife.adapters.xcomponent.catalog import catalog
8
+
9
+
10
+ @catalog.component
11
+ def P(
12
+ children: XNode,
13
+ globals: Mapping[str, str],
14
+ id: str | None = None,
15
+ class_: str | None = None,
16
+ ) -> str:
17
+ """
18
+ <p> html tag rendering
19
+
20
+ :param children: child node.
21
+ :param globals: injected root component globals variables.
22
+ :param id: unique identifier of the element.
23
+ :param class_: css class for the node, defaults to
24
+ :attr:`fastlife.template_globals.Globals.P_CLASS`.
25
+ """
26
+ return """
27
+ <p id={id} class={class_ or globals.P_CLASS}>
28
+ {children}
29
+ </p>
30
+ """
@@ -0,0 +1,145 @@
1
+ """Titles"""
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from xcomponent import XNode
6
+
7
+ from fastlife.adapters.xcomponent.catalog import catalog
8
+
9
+
10
+ @catalog.component
11
+ def H1(
12
+ children: XNode,
13
+ globals: Mapping[str, str],
14
+ id: str | None = None,
15
+ class_: str | None = None,
16
+ ) -> str:
17
+ """
18
+ <h1> html tag rendering
19
+
20
+ :param children: child node.
21
+ :param globals: injected root component globals variables.
22
+ :param id: unique identifier of the element.
23
+ :param class_: css class for the node, defaults to
24
+ :attr:`fastlife.template_globals.Globals.H1_CLASS`.
25
+ """
26
+ return """
27
+ <h1 id={id} class={class_ or globals.H1_CLASS}>
28
+ {children}
29
+ </h1>
30
+ """
31
+
32
+
33
+ @catalog.component
34
+ def H2(
35
+ children: XNode,
36
+ globals: Mapping[str, str],
37
+ id: str | None = None,
38
+ class_: str | None = None,
39
+ ) -> str:
40
+ """
41
+ <h2> html tag rendering
42
+
43
+ :param children: child node.
44
+ :param globals: injected root component globals variables.
45
+ :param id: unique identifier of the element.
46
+ :param class_: css class for the node, defaults to
47
+ :attr:`fastlife.template_globals.Globals.H2_CLASS`.
48
+ """
49
+ return """
50
+ <h2 id={id} class={class_ or globals.H2_CLASS}>
51
+ {children}
52
+ </h2>
53
+ """
54
+
55
+
56
+ @catalog.component
57
+ def H3(
58
+ children: XNode,
59
+ globals: Mapping[str, str],
60
+ id: str | None = None,
61
+ class_: str | None = None,
62
+ ) -> str:
63
+ """
64
+ <h3> html tag rendering
65
+
66
+ :param children: child node.
67
+ :param globals: injected root component globals variables.
68
+ :param id: unique identifier of the element.
69
+ :param class_: css class for the node, defaults to
70
+ :attr:`fastlife.template_globals.Globals.H3_CLASS`.
71
+ """
72
+ return """
73
+ <h3 id={id} class={class_ or globals.H3_CLASS}>
74
+ {children}
75
+ </h3>
76
+ """
77
+
78
+
79
+ @catalog.component
80
+ def H4(
81
+ children: XNode,
82
+ globals: Mapping[str, str],
83
+ id: str | None = None,
84
+ class_: str | None = None,
85
+ ) -> str:
86
+ """
87
+ <h4> html tag rendering
88
+
89
+ :param children: child node.
90
+ :param globals: injected root component globals variables.
91
+ :param id: unique identifier of the element.
92
+ :param class_: css class for the node, defaults to
93
+ :attr:`fastlife.template_globals.Globals.H4_CLASS`.
94
+ """
95
+ return """
96
+ <h4 id={id} class={class_ or globals.H4_CLASS}>
97
+ {children}
98
+ </h4>
99
+ """
100
+
101
+
102
+ @catalog.component
103
+ def H5(
104
+ children: XNode,
105
+ globals: Mapping[str, str],
106
+ id: str | None = None,
107
+ class_: str | None = None,
108
+ ) -> str:
109
+ """
110
+ <h5> html tag rendering
111
+
112
+ :param children: child node.
113
+ :param globals: injected root component globals variables.
114
+ :param id: unique identifier of the element.
115
+ :param class_: css class for the node, defaults to
116
+ :attr:`fastlife.template_globals.Globals.H5_CLASS`.
117
+ """
118
+ return """
119
+ <h5 id={id} class={class_ or globals.H5_CLASS}>
120
+ {children}
121
+ </h5>
122
+ """
123
+
124
+
125
+ @catalog.component
126
+ def H6(
127
+ children: XNode,
128
+ globals: Mapping[str, str],
129
+ id: str | None = None,
130
+ class_: str | None = None,
131
+ ) -> str:
132
+ """
133
+ <h6> html tag rendering
134
+
135
+ :param children: child node.
136
+ :param globals: injected root component globals variables.
137
+ :param id: unique identifier of the element.
138
+ :param class_: css class for the node, defaults to
139
+ :attr:`fastlife.template_globals.Globals.H6_CLASS`.
140
+ """
141
+ return """
142
+ <h6 id={id} class={class_ or globals.H6_CLASS}>
143
+ {children}
144
+ </h6>
145
+ """
File without changes
@@ -0,0 +1,93 @@
1
+ """Use ``<Icon>`` to load icons."""
2
+
3
+ import html
4
+ import re
5
+ from functools import cache
6
+ from importlib import util
7
+ from pathlib import Path
8
+ from typing import Literal
9
+ from zipfile import ZipFile
10
+
11
+ from fastlife.adapters.xcomponent.catalog import catalog
12
+
13
+
14
+ @cache
15
+ def icon_path() -> Path:
16
+ spec = util.find_spec("heroicons")
17
+ if spec is None or spec.origin is None:
18
+ raise ValueError("Install heroicons first") # coverage: ignore
19
+ return Path(spec.origin).parent
20
+
21
+
22
+ @cache
23
+ def get_from_zip(
24
+ name: str,
25
+ mode: str,
26
+ ) -> str | None:
27
+ iconzip = icon_path() / "heroicons.zip"
28
+ with ZipFile(iconzip, "r") as zip_ref:
29
+ file_list = zip_ref.namelist()
30
+ for filepath in file_list:
31
+ if filepath == f"{mode}/{name}.svg":
32
+ with zip_ref.open(filepath) as file:
33
+ return file.read().decode("utf-8")
34
+ return None
35
+
36
+
37
+ @catalog.function
38
+ def load_icon(
39
+ name: str,
40
+ mode: str,
41
+ id: str | None = None,
42
+ title: str | None = None,
43
+ class_: str | None = None,
44
+ ) -> str:
45
+ """
46
+ Function to load the icon behind the scene for the ``<Icon>`` component.
47
+
48
+ :param name: name of the icon.
49
+ :param mode: mode of the hero icon.
50
+ :param id: dom unique identifier.
51
+ :param title: title that can be bubble to the icon.
52
+ :param class_: css class to set.
53
+ """
54
+ icon = get_from_zip(name, mode)
55
+ if not icon:
56
+ return ""
57
+
58
+ attrs = ""
59
+ if id:
60
+ attrs += f' id="{html.escape(id)}"'
61
+ if class_:
62
+ attrs += f' class="{html.escape(class_)}"'
63
+ if attrs:
64
+ icon = icon.replace(' viewBox="', f'{attrs} viewBox="', 1)
65
+ if title:
66
+ icon = re.sub(
67
+ r"(<path[^>]*?)/>",
68
+ rf"\1><title>{html.escape(title)}</title></path>",
69
+ icon,
70
+ )
71
+ return icon
72
+
73
+
74
+ @catalog.component
75
+ def Icon(
76
+ name: str,
77
+ mode: Literal["micro", "mini", "outline", "solid"] = "solid",
78
+ id: str | None = None,
79
+ title: str | None = None,
80
+ class_: str | None = None,
81
+ ) -> str:
82
+ """
83
+ Add an icon from hero icon package. The svg is already injected to the DOM.
84
+
85
+ :param name: name of the icon.
86
+ :param mode: mode of the hero icon.
87
+ :param id: dom unique identifier.
88
+ :param title: title that can be bubble to the icon.
89
+ :param class_: css class to set.
90
+ """
91
+ return """
92
+ <>{load_icon(name, mode, id, title, class_)}</>
93
+ """
File without changes
@@ -0,0 +1,121 @@
1
+ from xcomponent import XNode
2
+
3
+ from fastlife.adapters.xcomponent.catalog import catalog
4
+
5
+
6
+ @catalog.component
7
+ def ErrorText(
8
+ text: str,
9
+ class_: str,
10
+ ) -> str:
11
+ """
12
+ display an error for a field.
13
+
14
+ :param text: error message
15
+ :param class_: css class for the node, defaults to
16
+ :attr:`fastlife.template_globals.Globals.ERROR_CLASS`.
17
+ """
18
+ return """
19
+ <span class={class_ or globals.ERROR_CLASS}>{text}</span>
20
+ """
21
+
22
+
23
+ @catalog.component
24
+ def OptionalErrorText(
25
+ text: str | None,
26
+ class_: str | None = None,
27
+ ) -> str:
28
+ """
29
+ display an error for a field.
30
+
31
+ :param text: error message
32
+ :param class_: css class for the node, defaults to
33
+ :attr:`fastlife.template_globals.Globals.ERROR_CLASS`.
34
+ """
35
+ return """
36
+ <>
37
+ {
38
+ if text {
39
+ <ErrorText text={text} class={class_} />
40
+ }
41
+ }
42
+ </>
43
+ """
44
+
45
+
46
+ @catalog.component
47
+ def FatalError(
48
+ message: str | None,
49
+ class_: str | None = None,
50
+ icon_class: str | None = None,
51
+ text_class: str | None = None,
52
+ ) -> str:
53
+ """
54
+ display an error for a field.
55
+
56
+ :param message: error message
57
+ :param class_: css class for the node, defaults to
58
+ :attr:`fastlife.template_globals.Globals.FATAL_ERROR_CLASS`.
59
+ :param icon_class: css class for the node, defaults to :attr:`fastlife.template_globals.Globals.FATAL_ERROR_ICON_CLASS`.
60
+ :param text_class: css class for the node, defaults to :attr:`fastlife.template_globals.Globals.FATAL_ERROR_TEXT_CLASS`.
61
+ """
62
+ return """
63
+ <>
64
+ {
65
+ if message {
66
+ <div class={class_ or globals.FATAL_ERROR_CLASS} role="alert">
67
+ <Icon name="fire" class={icon_class or globals.FATAL_ERROR_ICON_CLASS} />
68
+ <span class={text_class or globals.FATAL_ERROR_TEXT_CLASS}>{message}</span>
69
+ </div>
70
+ }
71
+ }
72
+ </>
73
+ """
74
+
75
+
76
+ @catalog.component
77
+ def Hint(text: str | None) -> str:
78
+ """
79
+ Display a hint message for a field.
80
+
81
+ :param text: hint text.
82
+ """
83
+ return """
84
+ <>
85
+ {
86
+ if text {
87
+ <span class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
88
+ {text}
89
+ </span>
90
+ }
91
+ }
92
+ </>
93
+ """
94
+
95
+
96
+ @catalog.component
97
+ def Widget(
98
+ widget_id: str,
99
+ removable: bool,
100
+ children: XNode,
101
+ ) -> str:
102
+ """
103
+ Base component for widget.
104
+
105
+ :param widget_id: widget to display.
106
+ :param removable: Set to true to add a remove button
107
+ """
108
+ return """
109
+ <div id={widget_id + "-container"}>
110
+ {children}
111
+ {
112
+ if removable {
113
+ <Button type="button"
114
+ onclick={"document.getElementById('" + widget_id + "-container').remove()"}
115
+ >
116
+ Remove
117
+ </Button>
118
+ }
119
+ }
120
+ </div>
121
+ """
@@ -0,0 +1 @@
1
+ """Factory for widgets in order to edit pydantic model."""
@@ -0,0 +1,40 @@
1
+ """Abstract class for builder."""
2
+
3
+ import abc
4
+ from collections.abc import Mapping
5
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
6
+
7
+ from pydantic.fields import FieldInfo
8
+
9
+ from fastlife.adapters.xcomponent.pydantic_form.widgets.base import Widget
10
+
11
+ if TYPE_CHECKING:
12
+ from .factory import ( # coverage: ignore
13
+ WidgetFactory,
14
+ )
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ class BaseWidgetBuilder(abc.ABC, Generic[T]):
20
+ """Base class for the builder of widget."""
21
+
22
+ def __init__(self, factory: "WidgetFactory") -> None:
23
+ self.factory = factory
24
+
25
+ @abc.abstractmethod
26
+ def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
27
+ """Return true if the builder accept to build a widget for this type."""
28
+
29
+ @abc.abstractmethod
30
+ def build(
31
+ self,
32
+ *,
33
+ field_name: str,
34
+ field_type: type[Any],
35
+ field: FieldInfo | None,
36
+ value: T | None,
37
+ form_errors: Mapping[str, Any],
38
+ removable: bool,
39
+ ) -> Widget[T]:
40
+ """Build the widget"""
@@ -0,0 +1,45 @@
1
+ """Handle boolean values."""
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ from pydantic.fields import FieldInfo
7
+
8
+ from fastlife.adapters.xcomponent.pydantic_form.widget_factory.base import (
9
+ BaseWidgetBuilder,
10
+ )
11
+ from fastlife.adapters.xcomponent.pydantic_form.widgets.boolean import BooleanWidget
12
+
13
+
14
+ class BoolBuilder(BaseWidgetBuilder[bool]):
15
+ """Builder for boolean."""
16
+
17
+ def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
18
+ """True for boolean."""
19
+ return issubclass(typ, bool)
20
+
21
+ def build(
22
+ self,
23
+ *,
24
+ field_name: str,
25
+ field_type: type[Any],
26
+ field: FieldInfo | None,
27
+ value: bool | None,
28
+ form_errors: Mapping[str, Any],
29
+ removable: bool,
30
+ ) -> BooleanWidget:
31
+ """Build the widget."""
32
+ return BooleanWidget(
33
+ name=field_name,
34
+ removable=removable,
35
+ title=field.title or "" if field else "",
36
+ hint=field.description if field else None,
37
+ aria_label=(
38
+ field.json_schema_extra.get("aria_label") # type:ignore
39
+ if field and field.json_schema_extra
40
+ else None
41
+ ),
42
+ token=self.factory.token,
43
+ value=value or False,
44
+ error=form_errors.get(field_name),
45
+ )
@@ -0,0 +1,50 @@
1
+ """Handle EmailStr pydantic type."""
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ from pydantic.fields import FieldInfo
7
+ from pydantic.networks import EmailStr
8
+
9
+ from fastlife.adapters.xcomponent.pydantic_form.widget_factory.base import (
10
+ BaseWidgetBuilder,
11
+ )
12
+ from fastlife.adapters.xcomponent.pydantic_form.widgets.text import TextWidget
13
+ from fastlife.domain.model.types import Builtins
14
+
15
+
16
+ class EmailStrBuilder(BaseWidgetBuilder[Builtins]):
17
+ """Builder for Pydantic EmailStr."""
18
+
19
+ def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
20
+ """True for EmailStr."""
21
+ return issubclass(typ, EmailStr) # type: ignore
22
+
23
+ def build(
24
+ self,
25
+ *,
26
+ field_name: str,
27
+ field_type: type[Any],
28
+ field: FieldInfo | None,
29
+ value: Builtins | None,
30
+ form_errors: Mapping[str, Any],
31
+ removable: bool,
32
+ ) -> TextWidget:
33
+ """Build the widget."""
34
+ return TextWidget(
35
+ name=field_name,
36
+ input_type="email",
37
+ placeholder=str(field.examples[0]) if field and field.examples else None,
38
+ removable=removable,
39
+ title=field.title or "" if field else "",
40
+ hint=field.description if field else None,
41
+ aria_label=(
42
+ field.json_schema_extra.get("aria_label") # type:ignore
43
+ if field and field.json_schema_extra
44
+ else None
45
+ ),
46
+ token=self.factory.token,
47
+ value=str(value),
48
+ error=form_errors.get(field_name),
49
+ autocomplete="email",
50
+ )