fastlifeweb 0.27.1__py3-none-any.whl → 0.28.1__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 +12 -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/config/configurator.py +8 -6
- fastlife/domain/model/template.py +6 -0
- fastlife/service/csrf.py +1 -1
- fastlife/service/templates.py +44 -2
- fastlife/views/pydantic_form.py +9 -9
- {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/METADATA +5 -2
- {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/RECORD +52 -14
- {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/WHEEL +0 -0
- {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/entry_points.txt +0 -0
- {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.28.1 - Released on 2025-09-20
|
2
|
+
* Fix jinjax dependencies.
|
3
|
+
* Fix documentation generation.
|
4
|
+
|
5
|
+
## 0.28.0 - Released on 2025-09-19
|
6
|
+
* Introduce new template engine xcomponent that will replace jinjax.
|
7
|
+
* At the moment, the template engine has to be installed as an extra
|
8
|
+
dependencie, none of them will be installed by default.
|
9
|
+
install fastlifeweb[jinjax] insted of fastlife for the compatibility.
|
10
|
+
The new template engine, xcomponent, has to be installed as
|
11
|
+
fastlifeweb[xcomponent].
|
12
|
+
|
1
13
|
## 0.27.1 - Released on 2025-07-10
|
2
14
|
* Add response_model to @resource_view in order to document views that use StreamResponse.
|
3
15
|
|
fastlife/__init__.py
CHANGED
@@ -41,7 +41,7 @@ from .domain.model.security_policy import (
|
|
41
41
|
Unauthenticated,
|
42
42
|
Unauthorized,
|
43
43
|
)
|
44
|
-
from .domain.model.template import JinjaXTemplate
|
44
|
+
from .domain.model.template import JinjaXTemplate, XTemplate
|
45
45
|
|
46
46
|
# from .request.form_data import model
|
47
47
|
from .service.registry import DefaultRegistry, GenericRegistry, TRegistry, TSettings
|
@@ -105,6 +105,7 @@ __all__ = [
|
|
105
105
|
"TIdentity",
|
106
106
|
# Template
|
107
107
|
"JinjaXTemplate",
|
108
|
+
"XTemplate",
|
108
109
|
# i18n
|
109
110
|
"Localizer",
|
110
111
|
"TranslatableStringFactory",
|
@@ -17,6 +17,7 @@ from fastlife import Request
|
|
17
17
|
from fastlife.adapters.fastapi.form import FormModel
|
18
18
|
from fastlife.adapters.fastapi.localizer import get_localizer
|
19
19
|
from fastlife.adapters.jinjax.widget_factory.factory import WidgetFactory
|
20
|
+
from fastlife.config import Configurator, configure
|
20
21
|
from fastlife.domain.model.template import InlineTemplate
|
21
22
|
|
22
23
|
if TYPE_CHECKING:
|
@@ -162,3 +163,10 @@ class JinjaxEngine(AbstractTemplateRendererFactory):
|
|
162
163
|
self.catalog,
|
163
164
|
request,
|
164
165
|
)
|
166
|
+
|
167
|
+
|
168
|
+
@configure
|
169
|
+
def includeme(conf: Configurator) -> None:
|
170
|
+
conf.add_renderer(
|
171
|
+
conf.registry.settings.jinjax_file_ext, JinjaxEngine(conf.registry.settings)
|
172
|
+
)
|
@@ -23,7 +23,7 @@ class UnionWidget(Widget[TWidget]):
|
|
23
23
|
<div id="{{id}}">
|
24
24
|
<Details>
|
25
25
|
<Summary :id="id + '-union-summary'">
|
26
|
-
<H3 :class="H3_SUMMARY_CLASS">{
|
26
|
+
<H3 :class="H3_SUMMARY_CLASS">{ globals.gettext(title) }</H3>
|
27
27
|
<pydantic_form.Error :text="error" />
|
28
28
|
</Summary>
|
29
29
|
<div hx-sync="this" id="{{id}}-child">
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""Collapsible components, using details/summary."""
|
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 Details(
|
12
|
+
children: XNode,
|
13
|
+
globals: Mapping[str, str],
|
14
|
+
id: str | None = None,
|
15
|
+
class_: str | None = None,
|
16
|
+
open: bool = True,
|
17
|
+
) -> str:
|
18
|
+
"""
|
19
|
+
Produce a ``<details>`` html node in order to create a collapsible box.
|
20
|
+
|
21
|
+
.. code-block:: html
|
22
|
+
|
23
|
+
<Details>
|
24
|
+
<Summary id="my-summary">
|
25
|
+
<H3 class={globals.H3_SUMMARY_CLASS}>A title</H3>
|
26
|
+
</Summary>
|
27
|
+
<div>
|
28
|
+
Some content
|
29
|
+
</div>
|
30
|
+
</Details>
|
31
|
+
|
32
|
+
:param id: unique identifier of the element.
|
33
|
+
:param class_: css class for the node, defaults to
|
34
|
+
:attr:`fastlife.template_globals.Globals.DETAILS_CLASS`.
|
35
|
+
:param open: open/close state.
|
36
|
+
"""
|
37
|
+
return """
|
38
|
+
<details id={id} class={class_ or globals.DETAILS_CLASS} open={open}>
|
39
|
+
{children}
|
40
|
+
</details>
|
41
|
+
"""
|
42
|
+
|
43
|
+
|
44
|
+
@catalog.component
|
45
|
+
def Summary(
|
46
|
+
children: XNode,
|
47
|
+
globals: Mapping[str, str],
|
48
|
+
id: str | None = None,
|
49
|
+
class_: str | None = None,
|
50
|
+
open: bool = True,
|
51
|
+
) -> str:
|
52
|
+
"""
|
53
|
+
Create html ``<summary>`` node for the
|
54
|
+
:func:`fastlife.adapters.xcomponent.html.collapsible.Details` component.
|
55
|
+
|
56
|
+
:param id: unique identifier of the element.
|
57
|
+
:param class_: css class for the node, defaults to
|
58
|
+
:attr:`fastlife.template_globals.Globals.SUMMARY_CLASS`.
|
59
|
+
:param open: Open or collapse the content of the details.
|
60
|
+
"""
|
61
|
+
return """
|
62
|
+
<summary id={id} class={class_ or globals.SUMMARY_CLASS}
|
63
|
+
style="list-style: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;"
|
64
|
+
onclick={"document.getElementById('" + id + "-icon').classList.toggle('rotate-90')"}>
|
65
|
+
<Icon name="chevron-right" id={id + '-icon'}
|
66
|
+
class={
|
67
|
+
if open {
|
68
|
+
"w-8 h-8 transform transition-transform duration-300 rotate-90"
|
69
|
+
}
|
70
|
+
else {
|
71
|
+
"w-8 h-8 transform transition-transform duration-300"
|
72
|
+
}
|
73
|
+
} />
|
74
|
+
{ children }
|
75
|
+
</summary>
|
76
|
+
"""
|
@@ -0,0 +1,437 @@
|
|
1
|
+
from collections.abc import Mapping
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from xcomponent import XNode
|
5
|
+
|
6
|
+
from fastlife.adapters.xcomponent.catalog import catalog
|
7
|
+
|
8
|
+
|
9
|
+
@catalog.component
|
10
|
+
def Form(
|
11
|
+
children: XNode,
|
12
|
+
globals: Mapping[str, str],
|
13
|
+
id: str | None = None,
|
14
|
+
class_: str | None = None,
|
15
|
+
method: Literal["get", "post"] | None = None,
|
16
|
+
action: str | None = None,
|
17
|
+
hx_target: str | None = None,
|
18
|
+
hx_select: str | None = None,
|
19
|
+
hx_swap: str | None = None,
|
20
|
+
hx_post: str | Literal[True] | None = None,
|
21
|
+
hx_disable: Literal[True] | None = None,
|
22
|
+
) -> str:
|
23
|
+
return """
|
24
|
+
<form
|
25
|
+
id={id}
|
26
|
+
class={class_ or globals.FORM_CLASS}
|
27
|
+
hx-disable={hx_disable}
|
28
|
+
hx-post={hx_post}
|
29
|
+
hx-select={hx_select}
|
30
|
+
hx-swap={hx_swap}
|
31
|
+
hx-target={hx_target}
|
32
|
+
action={action}
|
33
|
+
method={method}
|
34
|
+
>
|
35
|
+
<CsrfToken />
|
36
|
+
{ children }
|
37
|
+
</form>
|
38
|
+
"""
|
39
|
+
|
40
|
+
|
41
|
+
@catalog.component
|
42
|
+
def CsrfToken(globals: dict[str, str]) -> str:
|
43
|
+
return """
|
44
|
+
<Hidden
|
45
|
+
name={globals.request.csrf_token.name}
|
46
|
+
value={globals.request.csrf_token.value}
|
47
|
+
/>
|
48
|
+
"""
|
49
|
+
|
50
|
+
|
51
|
+
@catalog.component
|
52
|
+
def Input(
|
53
|
+
name: str,
|
54
|
+
value: str = "",
|
55
|
+
type: str = "text",
|
56
|
+
id: str | None = None,
|
57
|
+
class_: str | None = None,
|
58
|
+
aria_label: str | None = None,
|
59
|
+
placeholder: str | None = None,
|
60
|
+
inputmode: Literal[
|
61
|
+
"none", "text", "decimal", "numeric", "tel", "search", "email", "url"
|
62
|
+
]
|
63
|
+
| None = None,
|
64
|
+
autocomplete: Literal[
|
65
|
+
"on",
|
66
|
+
"off",
|
67
|
+
"name",
|
68
|
+
"username",
|
69
|
+
"current-password",
|
70
|
+
"new-password",
|
71
|
+
"one-time-code",
|
72
|
+
"email",
|
73
|
+
"tel",
|
74
|
+
"organization",
|
75
|
+
"street-address",
|
76
|
+
"address-line1",
|
77
|
+
"address-line2",
|
78
|
+
"address-line3",
|
79
|
+
"postal-code",
|
80
|
+
"country",
|
81
|
+
"country-name",
|
82
|
+
"cc-name",
|
83
|
+
"cc-number",
|
84
|
+
"cc-exp",
|
85
|
+
"cc-csc",
|
86
|
+
"tel-country-code",
|
87
|
+
"tel-national",
|
88
|
+
"tel-area-code",
|
89
|
+
"tel-local",
|
90
|
+
"tel-extension",
|
91
|
+
"bday",
|
92
|
+
"bday-day",
|
93
|
+
"bday-month",
|
94
|
+
"bday-year",
|
95
|
+
"transaction-amount",
|
96
|
+
"transaction-currency",
|
97
|
+
]
|
98
|
+
| None = None,
|
99
|
+
autofocus: bool = False,
|
100
|
+
) -> str:
|
101
|
+
return """
|
102
|
+
<input
|
103
|
+
type={type}
|
104
|
+
name={name}
|
105
|
+
value={value}
|
106
|
+
id={id}
|
107
|
+
aria-label={ aria_label }
|
108
|
+
placeholder={ placeholder }
|
109
|
+
inputmode={inputmode}
|
110
|
+
autocomplete={autocomplete}
|
111
|
+
class={class_ or globals.INPUT_CLASS}
|
112
|
+
autofocus={autofocus}
|
113
|
+
/>
|
114
|
+
"""
|
115
|
+
|
116
|
+
|
117
|
+
@catalog.component
|
118
|
+
def Hidden(
|
119
|
+
name: str,
|
120
|
+
value: str,
|
121
|
+
id: str | None = None,
|
122
|
+
) -> str:
|
123
|
+
return """
|
124
|
+
<input name={name} value={value} type="hidden" id={id} />
|
125
|
+
"""
|
126
|
+
|
127
|
+
|
128
|
+
@catalog.component
|
129
|
+
def Button(
|
130
|
+
children: XNode,
|
131
|
+
globals: Mapping[str, str],
|
132
|
+
type: Literal["submit", "button", "reset"] = "submit",
|
133
|
+
id: str | None = None,
|
134
|
+
class_: str | None = None,
|
135
|
+
name: str = "action",
|
136
|
+
value: str = "submit",
|
137
|
+
hidden: bool = False,
|
138
|
+
aria_label: str | None = None,
|
139
|
+
onclick: str | None = None,
|
140
|
+
hx_target: str | None = None,
|
141
|
+
hx_swap: str | None = None,
|
142
|
+
hx_select: str | None = None,
|
143
|
+
hx_on_after_request: str | None = None,
|
144
|
+
hx_vals: str | None = None,
|
145
|
+
hx_confirm: str | None = None,
|
146
|
+
hx_get: str | None = None,
|
147
|
+
hx_post: str | None = None,
|
148
|
+
hx_put: str | None = None,
|
149
|
+
hx_patch: str | None = None,
|
150
|
+
hx_delete: str | None = None,
|
151
|
+
hx_params: str | None = None,
|
152
|
+
hx_push_url: bool = False,
|
153
|
+
full_width: bool = False,
|
154
|
+
) -> str:
|
155
|
+
return """
|
156
|
+
<button
|
157
|
+
type={type}
|
158
|
+
name={name}
|
159
|
+
value={value}
|
160
|
+
id={id}
|
161
|
+
hx-target={hx_target}
|
162
|
+
hx-swap={hx_swap}
|
163
|
+
hx-select={hx_select}
|
164
|
+
onclick={onclick}
|
165
|
+
hx-on::after-request={hx_on_after_request}
|
166
|
+
hx-vals={hx_vals}
|
167
|
+
hx-confirm={hx_confirm}
|
168
|
+
hx-get={hx_get}
|
169
|
+
hx-post={hx_post}
|
170
|
+
hx-put={hx_put}
|
171
|
+
hx-patch={hx_patch}
|
172
|
+
hx-delete={hx_delete}
|
173
|
+
hx-push-url={hx_push_url}
|
174
|
+
hx-params={hx_params}
|
175
|
+
aria-label={aria_label}
|
176
|
+
class={
|
177
|
+
if full_width {
|
178
|
+
"w-full " + (class_ or globals.BUTTON_CLASS)
|
179
|
+
}
|
180
|
+
else {
|
181
|
+
class_ or globals.BUTTON_CLASS
|
182
|
+
}
|
183
|
+
}
|
184
|
+
hidden={hidden}
|
185
|
+
>
|
186
|
+
{children}
|
187
|
+
</button>
|
188
|
+
"""
|
189
|
+
|
190
|
+
|
191
|
+
@catalog.component
|
192
|
+
def Checkbox(
|
193
|
+
name: str,
|
194
|
+
globals: Mapping[str, str],
|
195
|
+
id: str | None = None,
|
196
|
+
class_: str | None = None,
|
197
|
+
value: str | None = None,
|
198
|
+
checked: bool = False,
|
199
|
+
) -> str:
|
200
|
+
"""
|
201
|
+
Create html ``<input type="checkbox" />`` node.
|
202
|
+
|
203
|
+
:param name: Name of the checkbox
|
204
|
+
:param id: unique identifier of the element
|
205
|
+
:param class_: css class for the node, defaults to
|
206
|
+
:attr:`fastlife.template_globals.Globals.CHECKBOX_CLASS`
|
207
|
+
:param value: http submitted value if the checkbox is checked
|
208
|
+
:param checked: Initialized the checkbox as ticked
|
209
|
+
"""
|
210
|
+
return """
|
211
|
+
<input
|
212
|
+
name={name}
|
213
|
+
type="checkbox"
|
214
|
+
id={id}
|
215
|
+
value={value}
|
216
|
+
class={class_ or globals.CHECKBOX_CLASS }
|
217
|
+
checked={checked}
|
218
|
+
/>
|
219
|
+
"""
|
220
|
+
|
221
|
+
|
222
|
+
@catalog.component
|
223
|
+
def Password(
|
224
|
+
name: str,
|
225
|
+
globals: Mapping[str, str],
|
226
|
+
id: str | None = None,
|
227
|
+
class_: str | None = None,
|
228
|
+
aria_label: str | None = None,
|
229
|
+
placeholder: str | None = None,
|
230
|
+
autocomplete: Literal[
|
231
|
+
"on",
|
232
|
+
"off",
|
233
|
+
"current-password",
|
234
|
+
"new-password",
|
235
|
+
]
|
236
|
+
| None = None,
|
237
|
+
inputmode: Literal[
|
238
|
+
"none",
|
239
|
+
"text",
|
240
|
+
"numeric",
|
241
|
+
]
|
242
|
+
| None = None,
|
243
|
+
minlength: int | None = None,
|
244
|
+
maxlength: int | None = None,
|
245
|
+
pattern: str | None = None,
|
246
|
+
autofocus: bool = False,
|
247
|
+
required: bool = False,
|
248
|
+
readonly: bool = False,
|
249
|
+
) -> str:
|
250
|
+
"""
|
251
|
+
Produce ``<input type="password">`` node.
|
252
|
+
|
253
|
+
:param name: submitted name in the form
|
254
|
+
:param id: unique identifier of the element
|
255
|
+
:param class_: css class for the node, defaults to
|
256
|
+
:attr:`fastlife.template_globals.Globals.INPUT_CLASS`
|
257
|
+
:param aria_label: aria-label
|
258
|
+
:param placeholder: brief hint to the user as to what kind of information
|
259
|
+
is expected in the field
|
260
|
+
:param autocomplete: Define autocomplete mode
|
261
|
+
:param inputmode: Define a virtual keyboard layout
|
262
|
+
:param minlength: Minimum length
|
263
|
+
:param maxlength: Maximum length
|
264
|
+
:param pattern: Must match a pattern
|
265
|
+
:param autofocus: Give the focus
|
266
|
+
:param required: Mark as required field
|
267
|
+
:param readonly: Mark as readonly field
|
268
|
+
"""
|
269
|
+
return """
|
270
|
+
<input
|
271
|
+
name={name}
|
272
|
+
type="password"
|
273
|
+
id={id}
|
274
|
+
aria-label={aria_label}
|
275
|
+
placeholder={placeholder}
|
276
|
+
autocomplete={autocomplete}
|
277
|
+
inputmode={inputmode}
|
278
|
+
minlength={minlength}
|
279
|
+
maxlength={maxlength}
|
280
|
+
pattern={pattern}
|
281
|
+
class={class_ or globals.INPUT_CLASS}
|
282
|
+
autofocus={autofocus}
|
283
|
+
required={required}
|
284
|
+
readonly={readonly}
|
285
|
+
/>
|
286
|
+
"""
|
287
|
+
|
288
|
+
|
289
|
+
@catalog.component
|
290
|
+
def Label(
|
291
|
+
children: XNode,
|
292
|
+
globals: Mapping[str, str],
|
293
|
+
for_: str | None = None,
|
294
|
+
id: str | None = None,
|
295
|
+
class_: str | None = None,
|
296
|
+
) -> str:
|
297
|
+
"""
|
298
|
+
Produce ``<label>`` node.
|
299
|
+
|
300
|
+
:param for_: unique identifier of the target element.
|
301
|
+
:param id: unique identifier of the element.
|
302
|
+
:param class_: css class for the node, defaults to
|
303
|
+
:attr:`fastlife.template_globals.Globals.LABEL_CLASS`.
|
304
|
+
"""
|
305
|
+
return """
|
306
|
+
<label for={for_} class={class_ or globals.LABEL_CLASS} id={id}>
|
307
|
+
{children}
|
308
|
+
</label>
|
309
|
+
"""
|
310
|
+
|
311
|
+
|
312
|
+
@catalog.component
|
313
|
+
def Option(
|
314
|
+
children: XNode,
|
315
|
+
globals: Mapping[str, str],
|
316
|
+
value: str,
|
317
|
+
id: str | None = None,
|
318
|
+
selected: bool = False,
|
319
|
+
) -> str:
|
320
|
+
"""
|
321
|
+
Produce ``<option>`` node.
|
322
|
+
|
323
|
+
:param value: posted value after submitted the selected value
|
324
|
+
:param id: unique identifier of the element
|
325
|
+
:param selected: Used to select the option while rendering the form
|
326
|
+
"""
|
327
|
+
return """
|
328
|
+
<option value={value} id={id} selected={selected}>
|
329
|
+
{children}
|
330
|
+
</option>
|
331
|
+
"""
|
332
|
+
|
333
|
+
|
334
|
+
@catalog.component
|
335
|
+
def Select(
|
336
|
+
children: XNode,
|
337
|
+
globals: Mapping[str, str],
|
338
|
+
name: str,
|
339
|
+
id: str | None = None,
|
340
|
+
class_: str | None = None,
|
341
|
+
multiple: bool = False,
|
342
|
+
) -> str:
|
343
|
+
"""
|
344
|
+
Create html ``<select>`` node.
|
345
|
+
|
346
|
+
:param name: name of the submitted
|
347
|
+
:param id: unique identifier of the element
|
348
|
+
:param class_: css class for the node, defaults to
|
349
|
+
:attr:`fastlife.template_globals.Globals.SELECT_CLASS`
|
350
|
+
:param multiple: Mark as multiple
|
351
|
+
"""
|
352
|
+
return """
|
353
|
+
<select name={name} id={id} class={class_ or globals.SELECT_CLASS}
|
354
|
+
multiple={multiple}>
|
355
|
+
{children}
|
356
|
+
</select>
|
357
|
+
"""
|
358
|
+
|
359
|
+
|
360
|
+
@catalog.component
|
361
|
+
def Radio(
|
362
|
+
globals: Mapping[str, str],
|
363
|
+
label: str,
|
364
|
+
name: str,
|
365
|
+
value: str,
|
366
|
+
id: str | None = None,
|
367
|
+
checked: bool = False,
|
368
|
+
disabled: bool = False,
|
369
|
+
onclick: str | None = None,
|
370
|
+
div_class: str | None = None,
|
371
|
+
class_: str | None = None,
|
372
|
+
label_class: str | None = None,
|
373
|
+
) -> str:
|
374
|
+
"""
|
375
|
+
Produce a ``<input type="radio">`` with its associated label inside a div.
|
376
|
+
|
377
|
+
:param label: label text associated to the radio
|
378
|
+
:param name: name of the submitted
|
379
|
+
:param value: value that will be submitted if selected
|
380
|
+
:param id: unique identifier of the element
|
381
|
+
:param checked: Tick the radio button
|
382
|
+
:param disabled: Mark the radio button as disabled
|
383
|
+
:param onclick: execute some javascript while clicking
|
384
|
+
:param div_class: css class for the div node, defaults to
|
385
|
+
:attr:`fastlife.template_globals.Globals.RADIO_DIV_CLASS`
|
386
|
+
:param class_: css class for the input node, defaults to
|
387
|
+
:attr:`fastlife.template_globals.Globals.RADIO_INPUT_CLASS`
|
388
|
+
:param label_class: css class for the label node, defaults to
|
389
|
+
:attr:`fastlife.template_globals.Globals.RADIO_LABEL_CLASS`
|
390
|
+
"""
|
391
|
+
|
392
|
+
return """
|
393
|
+
<div class={div_class or globals.RADIO_DIV_CLASS}>
|
394
|
+
<input type="radio" name={name} id={id} value={value}
|
395
|
+
class={class_ or globals.RADIO_INPUT_CLASS}
|
396
|
+
onclick={onclick}
|
397
|
+
checked={checked}
|
398
|
+
disabled={disabled} />
|
399
|
+
<Label for={id} class={label_class or globals.RADIO_LABEL_CLASS}>
|
400
|
+
{label}
|
401
|
+
</Label>
|
402
|
+
</div>
|
403
|
+
"""
|
404
|
+
|
405
|
+
|
406
|
+
@catalog.component
|
407
|
+
def Textarea(
|
408
|
+
children: XNode,
|
409
|
+
globals: Mapping[str, str],
|
410
|
+
name: str,
|
411
|
+
id: str | None = None,
|
412
|
+
class_: str | None = None,
|
413
|
+
aria_label: str | None = None,
|
414
|
+
placeholder: str | None = None,
|
415
|
+
) -> str:
|
416
|
+
"""
|
417
|
+
html ``<textarea>`` node.
|
418
|
+
|
419
|
+
:param name: name of the submitted
|
420
|
+
:param id: unique identifier of the element
|
421
|
+
:param class_: css class for the node, defaults to
|
422
|
+
:attr:`fastlife.template_globals.Globals.INPUT_CLASS`
|
423
|
+
:param aria_label: aria-label
|
424
|
+
:param placeholder: brief hint to the user as to what kind of information
|
425
|
+
is expected in the field
|
426
|
+
"""
|
427
|
+
|
428
|
+
return """
|
429
|
+
<textarea
|
430
|
+
name={name}
|
431
|
+
id={id}
|
432
|
+
aria-label={aria_label}
|
433
|
+
placeholder={placeholder}
|
434
|
+
class={class_ or globals.INPUT_CLASS}>
|
435
|
+
{children}
|
436
|
+
</textarea>
|
437
|
+
"""
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"""Navigation component."""
|
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 A(
|
12
|
+
href: str,
|
13
|
+
children: XNode,
|
14
|
+
globals: Mapping[str, str],
|
15
|
+
id: str | None = None,
|
16
|
+
class_: str | None = None,
|
17
|
+
hx_target: str = "#maincontent",
|
18
|
+
hx_select: str | None = None,
|
19
|
+
hx_swap: str = "innerHTML show:body:top",
|
20
|
+
hx_push_url: bool = True,
|
21
|
+
hx_get: str | None = None,
|
22
|
+
hx_disable: bool | None = None,
|
23
|
+
hx_disabled_elt: str | None = None,
|
24
|
+
) -> str:
|
25
|
+
"""
|
26
|
+
Create html ``<a>`` node with htmx support by default.
|
27
|
+
The `hx-get` parameter is set with the href directly unless the
|
28
|
+
`disabled-htmx` attribute has been set.
|
29
|
+
|
30
|
+
:param href: Target link.
|
31
|
+
:param id: Unique identifier of the element.
|
32
|
+
:param class_: CSS class for the node, defaults to
|
33
|
+
:attr:`fastlife.template_globals.Globals.A_CLASS`.
|
34
|
+
:param hx_target: Target the element for swapping than the one issuing
|
35
|
+
the AJAX request.
|
36
|
+
:param hx_select: Select the content swapped from the response of the AJAX request.
|
37
|
+
:param hx_swap: Specify how the response will be swapped in relative to the target
|
38
|
+
of an AJAX request.
|
39
|
+
:param hx_push_url: Replace the browser URL with the link.
|
40
|
+
:param hx_get: Override the target link only for htmx request for component
|
41
|
+
rendering. href will be used if None.
|
42
|
+
:param disable_htmx: Do not add any `hx-*` attribute to the link.
|
43
|
+
"""
|
44
|
+
|
45
|
+
return """
|
46
|
+
<a
|
47
|
+
href={href}
|
48
|
+
id={id}
|
49
|
+
hx-disable={hx_disable}
|
50
|
+
hx-disabled-elt={hx_disabled_elt}
|
51
|
+
hx-get={hx_get or href}
|
52
|
+
hx-target={hx_target}
|
53
|
+
hx-swap={hx_swap}
|
54
|
+
hx-push-url={hx_push_url}
|
55
|
+
hx-select={hx_select}
|
56
|
+
class={class_ or globals.A_CLASS}
|
57
|
+
>
|
58
|
+
{children}
|
59
|
+
</a>
|
60
|
+
"""
|