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.
Files changed (52) hide show
  1. CHANGELOG.md +12 -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/config/configurator.py +8 -6
  44. fastlife/domain/model/template.py +6 -0
  45. fastlife/service/csrf.py +1 -1
  46. fastlife/service/templates.py +44 -2
  47. fastlife/views/pydantic_form.py +9 -9
  48. {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/METADATA +5 -2
  49. {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/RECORD +52 -14
  50. {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/WHEEL +0 -0
  51. {fastlifeweb-0.27.1.dist-info → fastlifeweb-0.28.1.dist-info}/entry_points.txt +0 -0
  52. {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">{{ gettext(title) }}</H3>
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,11 @@
1
+ from typing import Any
2
+
3
+ from xcomponent import Catalog
4
+
5
+ catalog = Catalog()
6
+ """The catalog to register components."""
7
+
8
+
9
+ @catalog.function(name="len")
10
+ def length(iterable: Any) -> int:
11
+ return len(iterable)
@@ -0,0 +1,7 @@
1
+ """
2
+ This module load the html components for the xcomponent template engine.
3
+
4
+ Those components are loaded, injected in the catalog using a
5
+ {meth}`config.include <fastlife.config.configurator.Configurator.include>`
6
+ done before the app is created.
7
+ """
@@ -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
+ """