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.
- CHANGELOG.md +11 -0
- fastlife/__init__.py +2 -1
- fastlife/adapters/jinjax/renderer.py +8 -0
- fastlife/adapters/jinjax/widgets/union.py +1 -1
- fastlife/adapters/xcomponent/__init__.py +1 -0
- fastlife/adapters/xcomponent/catalog.py +11 -0
- fastlife/adapters/xcomponent/html/__init__.py +7 -0
- fastlife/adapters/xcomponent/html/collapsible.py +76 -0
- fastlife/adapters/xcomponent/html/form.py +437 -0
- fastlife/adapters/xcomponent/html/nav.py +60 -0
- fastlife/adapters/xcomponent/html/table.py +130 -0
- fastlife/adapters/xcomponent/html/text.py +30 -0
- fastlife/adapters/xcomponent/html/title.py +145 -0
- fastlife/adapters/xcomponent/icons/__init__.py +0 -0
- fastlife/adapters/xcomponent/icons/icons.py +93 -0
- fastlife/adapters/xcomponent/pydantic_form/__init__.py +0 -0
- fastlife/adapters/xcomponent/pydantic_form/components.py +121 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/__init__.py +1 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/base.py +40 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/bool_builder.py +45 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/emailstr_builder.py +50 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/enum_builder.py +49 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/factory.py +188 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/literal_builder.py +55 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/model_builder.py +66 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/secretstr_builder.py +48 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/sequence_builder.py +60 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/set_builder.py +85 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/simpletype_builder.py +48 -0
- fastlife/adapters/xcomponent/pydantic_form/widget_factory/union_builder.py +92 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/__init__.py +1 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/base.py +140 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/boolean.py +25 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/checklist.py +75 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/dropdown.py +72 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/hidden.py +25 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/mfa_code.py +25 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/model.py +49 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/sequence.py +74 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/text.py +121 -0
- fastlife/adapters/xcomponent/pydantic_form/widgets/union.py +81 -0
- fastlife/adapters/xcomponent/renderer.py +130 -0
- fastlife/assets/dist.css +4 -1
- fastlife/components/A.jinja +5 -1
- fastlife/config/configurator.py +7 -8
- fastlife/config/resources.py +9 -3
- fastlife/domain/model/template.py +6 -0
- fastlife/service/csrf.py +1 -1
- fastlife/service/templates.py +44 -2
- fastlife/template_globals.py +3 -0
- fastlife/views/pydantic_form.py +9 -9
- {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/METADATA +6 -3
- {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/RECORD +56 -18
- {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/WHEEL +1 -1
- {fastlifeweb-0.27.0.dist-info → fastlifeweb-0.28.0.dist-info}/entry_points.txt +0 -0
- {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
|
+
)
|