htpy-htmx 0.1.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.
- htpy_htmx/__init__.py +0 -0
- htpy_htmx/_types.py +107 -0
- htpy_htmx/ext.py +129 -0
- htpy_htmx/hx.py +509 -0
- htpy_htmx-0.1.0.dist-info/METADATA +83 -0
- htpy_htmx-0.1.0.dist-info/RECORD +9 -0
- htpy_htmx-0.1.0.dist-info/WHEEL +5 -0
- htpy_htmx-0.1.0.dist-info/licenses/LICENCE +21 -0
- htpy_htmx-0.1.0.dist-info/top_level.txt +1 -0
htpy_htmx/__init__.py
ADDED
|
File without changes
|
htpy_htmx/_types.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from typing import TypeAlias, Literal
|
|
2
|
+
|
|
3
|
+
_swap_style: TypeAlias = (
|
|
4
|
+
str # This allows extensions to add new swap_styles (like upsert and download)
|
|
5
|
+
| Literal[
|
|
6
|
+
"innerHTML",
|
|
7
|
+
"outerHTML",
|
|
8
|
+
"beforebegin",
|
|
9
|
+
"before",
|
|
10
|
+
"afterbegin",
|
|
11
|
+
"prepend",
|
|
12
|
+
"beforeend",
|
|
13
|
+
"append",
|
|
14
|
+
"afterend",
|
|
15
|
+
"after",
|
|
16
|
+
"delete",
|
|
17
|
+
"none",
|
|
18
|
+
"innerMorph",
|
|
19
|
+
"outerMorph",
|
|
20
|
+
"textContent",
|
|
21
|
+
"outerSync",
|
|
22
|
+
# "upsert", # Requires the hx-upsert extension.
|
|
23
|
+
# "download", # Required the hx-download extension.
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_selector: TypeAlias = (
|
|
28
|
+
str | Literal["this", "next", "previous", "body", "document", "window", "host"]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_event: TypeAlias = (
|
|
32
|
+
str
|
|
33
|
+
| Literal[
|
|
34
|
+
"load",
|
|
35
|
+
"revealed",
|
|
36
|
+
"intersect",
|
|
37
|
+
"submit",
|
|
38
|
+
"change",
|
|
39
|
+
"input",
|
|
40
|
+
"focus",
|
|
41
|
+
"blur",
|
|
42
|
+
"click",
|
|
43
|
+
"dblclick",
|
|
44
|
+
"mousedown",
|
|
45
|
+
"mouseup",
|
|
46
|
+
"mousemove",
|
|
47
|
+
"mouseenter",
|
|
48
|
+
"mouseleave",
|
|
49
|
+
"wheel",
|
|
50
|
+
"mousewheel",
|
|
51
|
+
"keyup",
|
|
52
|
+
"keydown",
|
|
53
|
+
"keypress",
|
|
54
|
+
"dragstart",
|
|
55
|
+
"drag",
|
|
56
|
+
"dragend",
|
|
57
|
+
"dragenter",
|
|
58
|
+
"dragover",
|
|
59
|
+
"dragleave",
|
|
60
|
+
"drop",
|
|
61
|
+
"play",
|
|
62
|
+
"pause",
|
|
63
|
+
"ended",
|
|
64
|
+
"volumechange",
|
|
65
|
+
"touchstart",
|
|
66
|
+
"touchend",
|
|
67
|
+
"touchmove",
|
|
68
|
+
"touchcancel",
|
|
69
|
+
"resize",
|
|
70
|
+
"scroll",
|
|
71
|
+
"select",
|
|
72
|
+
"search",
|
|
73
|
+
"invalid",
|
|
74
|
+
"contextmenu",
|
|
75
|
+
"reset",
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
_htmx_events: TypeAlias = Literal[
|
|
80
|
+
"htmx:config:request",
|
|
81
|
+
"htmx:before:request",
|
|
82
|
+
"htmx:after:request",
|
|
83
|
+
"htmx:finally:request",
|
|
84
|
+
"htmx:before:swap",
|
|
85
|
+
"htmx:after:swap",
|
|
86
|
+
"htmx:finally:swap",
|
|
87
|
+
"htmx:before:cleanup",
|
|
88
|
+
"htmx:after:cleanup",
|
|
89
|
+
"htmx:confirm",
|
|
90
|
+
"htmx:error",
|
|
91
|
+
"htmx:abort",
|
|
92
|
+
"htmx:before:init",
|
|
93
|
+
"htmx:after:init",
|
|
94
|
+
"htmx:before:process",
|
|
95
|
+
"htmx:after:process",
|
|
96
|
+
"htmx:before:history:update",
|
|
97
|
+
"htmx:after:history:update",
|
|
98
|
+
"htmx:after:history:push",
|
|
99
|
+
"htmx:after:history:replace",
|
|
100
|
+
"htmx:before:history:restore",
|
|
101
|
+
"htmx:before:viewTransition",
|
|
102
|
+
"htmx:after:viewTransition",
|
|
103
|
+
"htmx:before:response",
|
|
104
|
+
"htmx:before:settle",
|
|
105
|
+
"htmx:after:settle",
|
|
106
|
+
"htmx:response:error",
|
|
107
|
+
]
|
htpy_htmx/ext.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Literal
|
|
3
|
+
from ._types import _selector, _event
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def multipart(*, connect: str, close: _event, inherited: bool = False) -> dict[str, str]:
|
|
7
|
+
"""Stream HTML with multipart/mixed.
|
|
8
|
+
|
|
9
|
+
https://four.htmx.org/extensions/hx-multipart
|
|
10
|
+
"""
|
|
11
|
+
data: dict[str, str] = {}
|
|
12
|
+
if connect:
|
|
13
|
+
data[f"hx-multipart:connect{':inherited' if inherited else ''}"] = connect
|
|
14
|
+
if close:
|
|
15
|
+
data[f"hx-multipart:close{':inherited' if inherited else ''}"] = close
|
|
16
|
+
return data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sse(*, connect: _event = "", close: _event = "", inherited: bool = False) -> dict[str, str]:
|
|
20
|
+
"""Stream HTML with text/event-stream (SSE).
|
|
21
|
+
|
|
22
|
+
https://four.htmx.org/extensions/hx-sse
|
|
23
|
+
"""
|
|
24
|
+
data: dict[str, str] = {}
|
|
25
|
+
if connect:
|
|
26
|
+
data[f"hx-sse:connect{':inherited' if inherited else ''}"] = connect
|
|
27
|
+
if close:
|
|
28
|
+
data[f"hx-sse:close{':inherited' if inherited else ''}"] = close
|
|
29
|
+
return data
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ws(*, connect: _event = "", send: bool = False, inherited: bool = False) -> dict[str, str]:
|
|
33
|
+
"""Stream HTML and send data over WebSockets.
|
|
34
|
+
|
|
35
|
+
https://four.htmx.org/extensions/hx-ws
|
|
36
|
+
"""
|
|
37
|
+
data: dict[str, str] = {}
|
|
38
|
+
if connect:
|
|
39
|
+
data[f"hx-ws:connect{':inherited' if inherited else ''}"] = connect
|
|
40
|
+
if send:
|
|
41
|
+
data[f"hx-ws:send{':inherited' if inherited else ''}"] = ""
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def live(attribute: str, expression: str, *, inherited: bool = False) -> dict[str, str]:
|
|
46
|
+
"""Add reactive bindings to HTML.
|
|
47
|
+
|
|
48
|
+
https://four.htmx.org/extensions/hx-live
|
|
49
|
+
"""
|
|
50
|
+
return {f"hx-live:{attribute}{':inherited' if inherited else ''}": expression}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def optimistic(selector: _selector, *, inherited: bool = False) -> dict[str, str]:
|
|
54
|
+
"""Shows optimistic content during request.
|
|
55
|
+
|
|
56
|
+
Note: This is an extension attribute. To use it, you must include the optimistic extension.
|
|
57
|
+
https://four.htmx.org/reference/attributes/hx-optimistic
|
|
58
|
+
"""
|
|
59
|
+
return {f"hx-optimistic{':inherited' if inherited else ''}": selector}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def browser_indicator(show_indicator: bool = True, *, inherited: bool = False) -> dict[str, str]:
|
|
63
|
+
"""Show tab's spinner with hx-browser-indicator.
|
|
64
|
+
|
|
65
|
+
https://four.htmx.org/extensions/hx-browser-indicator
|
|
66
|
+
"""
|
|
67
|
+
return {f"hx-browser-indicator{':inherited' if inherited else ''}": str(show_indicator).lower()}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def prompt(prompt: str, *, inherited: bool = False) -> dict[str, str]:
|
|
71
|
+
"""Prompt before requests with hx-prompt='Reason?'
|
|
72
|
+
|
|
73
|
+
https://four.htmx.org/extensions/hx-prompt
|
|
74
|
+
"""
|
|
75
|
+
return {f"hx-prompt{':inherited' if inherited else ''}": prompt}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def preload(
|
|
79
|
+
event: Literal["mouseenter", "mouseover", "touchstart"], *, inherited: bool = False
|
|
80
|
+
) -> dict[str, str]:
|
|
81
|
+
"""Preloads content before user triggers request.
|
|
82
|
+
|
|
83
|
+
Note: This is an extension attribute. To use it, you must include the preload extension.
|
|
84
|
+
https://four.htmx.org/reference/attributes/hx-preload
|
|
85
|
+
"""
|
|
86
|
+
return {f"hx-preload{':inherited' if inherited else ''}": event}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def ptag(tag: str, *, inherited: bool = False) -> dict[str, str]:
|
|
90
|
+
"""Skip unchanged polls with HX-PTag: "v42".
|
|
91
|
+
|
|
92
|
+
https://four.htmx.org/extensions/hx-ptag
|
|
93
|
+
"""
|
|
94
|
+
return {f"hx-ptag{':inherited' if inherited else ''}": tag}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def history(save_to_cache: bool, *, inherited: bool = False) -> dict[str, str]:
|
|
98
|
+
"""Restore back/forward pages from sessionStorage.
|
|
99
|
+
|
|
100
|
+
https://four.htmx.org/extensions/hx-history-cache
|
|
101
|
+
"""
|
|
102
|
+
return {f"hx-history{':inherited' if inherited else ''}": str(save_to_cache).lower()}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def head(
|
|
106
|
+
strategy: Literal["merge", "append", "re-eval"], *, inherited: bool = False
|
|
107
|
+
) -> dict[str, str]:
|
|
108
|
+
"""Merge <head> tags with hx-head='merge'.
|
|
109
|
+
|
|
110
|
+
https://four.htmx.org/extensions/hx-head
|
|
111
|
+
"""
|
|
112
|
+
return {f"hx-head{':inherited' if inherited else ''}": strategy}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def targets(*selector: _selector, inherited: bool = False) -> dict[str, str]:
|
|
116
|
+
"""Target many elements.
|
|
117
|
+
|
|
118
|
+
Requires the hx-targets extension
|
|
119
|
+
https://four.htmx.org/extensions/hx-targets
|
|
120
|
+
"""
|
|
121
|
+
return {f"hx-targets{':inherited' if inherited else ''}": ", ".join(selector)}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def nonce(csp_nonce: str, *, inherited: bool = False) -> dict[str, str]:
|
|
125
|
+
"""Make htmx work under strict Content Security Policy.
|
|
126
|
+
|
|
127
|
+
https://four.htmx.org/extensions/hx-csp
|
|
128
|
+
"""
|
|
129
|
+
return {f"hx-nonce{':inherited' if inherited else ''}": csp_nonce}
|
htpy_htmx/hx.py
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import json
|
|
3
|
+
from typing import Literal, Any, overload
|
|
4
|
+
from htpy import Element
|
|
5
|
+
from ._types import _swap_style, _selector, _event, _htmx_events
|
|
6
|
+
|
|
7
|
+
partial = Element("hx-partial")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get(url: str, *, inherited: bool = False) -> dict[str, str]:
|
|
11
|
+
"""Issues GET request to specified URL.
|
|
12
|
+
|
|
13
|
+
https://four.htmx.org/reference/attributes/hx-get
|
|
14
|
+
"""
|
|
15
|
+
return {f"hx-get{':inherited' if inherited else ''}": url}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def post(url: str, *, inherited: bool = False) -> dict[str, str]:
|
|
19
|
+
"""Issues POST request to specified URL.
|
|
20
|
+
|
|
21
|
+
https://four.htmx.org/reference/attributes/hx-post
|
|
22
|
+
"""
|
|
23
|
+
return {f"hx-post{':inherited' if inherited else ''}": url}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def put(url: str, *, inherited: bool = False) -> dict[str, str]:
|
|
27
|
+
"""Issues PUT request to specified URL.
|
|
28
|
+
|
|
29
|
+
https://four.htmx.org/reference/attributes/hx-put
|
|
30
|
+
"""
|
|
31
|
+
return {f"hx-put{':inherited' if inherited else ''}": url}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def patch(url: str, *, inherited: bool = False) -> dict[str, str]:
|
|
35
|
+
"""Issues PATCH request to specified URL.
|
|
36
|
+
|
|
37
|
+
https://four.htmx.org/reference/attributes/hx-patch
|
|
38
|
+
"""
|
|
39
|
+
return {f"hx-patch{':inherited' if inherited else ''}": url}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def delete(url: str, *, inherited: bool = False) -> dict[str, str]:
|
|
43
|
+
"""Issues DELETE request to specified URL.
|
|
44
|
+
|
|
45
|
+
https://four.htmx.org/reference/attributes/hx-delete
|
|
46
|
+
"""
|
|
47
|
+
return {f"hx-delete{':inherited' if inherited else ''}": url}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def trigger_event(
|
|
51
|
+
event: _event,
|
|
52
|
+
filter: str = "",
|
|
53
|
+
once: bool = False,
|
|
54
|
+
changed: bool = False,
|
|
55
|
+
delay: str = "",
|
|
56
|
+
throttle: str = "",
|
|
57
|
+
from_: _selector = "",
|
|
58
|
+
target: _selector = "",
|
|
59
|
+
prevent: bool = False,
|
|
60
|
+
stop: bool = False,
|
|
61
|
+
consume: bool = False,
|
|
62
|
+
halt: bool = False,
|
|
63
|
+
capture: bool = False,
|
|
64
|
+
passive: bool = False,
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Helper function to generate event syntax for hx-trigger.
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
hx.trigger(hx.trigger_event("keyup", filter="key==Enter"), "click")
|
|
70
|
+
"""
|
|
71
|
+
text: str = event
|
|
72
|
+
if filter:
|
|
73
|
+
text += f"[{filter}]"
|
|
74
|
+
if once:
|
|
75
|
+
text += " once"
|
|
76
|
+
if changed:
|
|
77
|
+
text += " changed"
|
|
78
|
+
if delay:
|
|
79
|
+
text += f" delay:{delay}"
|
|
80
|
+
if throttle:
|
|
81
|
+
text += f" throttle:{throttle}"
|
|
82
|
+
if from_:
|
|
83
|
+
text += f" from:{from_}"
|
|
84
|
+
if target:
|
|
85
|
+
text += f" target:{target}"
|
|
86
|
+
if prevent:
|
|
87
|
+
text += " prevent"
|
|
88
|
+
if stop:
|
|
89
|
+
text += " stop"
|
|
90
|
+
if consume:
|
|
91
|
+
text += " consume"
|
|
92
|
+
if halt:
|
|
93
|
+
text += " halt"
|
|
94
|
+
if capture:
|
|
95
|
+
text += " capture"
|
|
96
|
+
if passive:
|
|
97
|
+
text += " passive"
|
|
98
|
+
return text
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def trigger(*event: _event, inherited: bool = False) -> dict[str, str]:
|
|
102
|
+
"""Controls when element issues requests.
|
|
103
|
+
|
|
104
|
+
https://four.htmx.org/reference/attributes/hx-trigger
|
|
105
|
+
"""
|
|
106
|
+
return {f"hx-trigger{':inherited' if inherited else ''}": ", ".join(event)}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def swap(
|
|
110
|
+
style: _swap_style,
|
|
111
|
+
*,
|
|
112
|
+
transition: bool | None = None,
|
|
113
|
+
swap: str = "",
|
|
114
|
+
settle: str = "",
|
|
115
|
+
ignoreTitle: bool | None = None,
|
|
116
|
+
scroll: Literal["top", "bottom"] | str = "",
|
|
117
|
+
scrollTarget: str = "",
|
|
118
|
+
show: Literal["top", "bottom", "none"] | str = "",
|
|
119
|
+
showTarget: str = "",
|
|
120
|
+
focusScroll: bool | None = None,
|
|
121
|
+
target: str = "",
|
|
122
|
+
strip: bool | None = None,
|
|
123
|
+
swapEmpty: bool | None = None,
|
|
124
|
+
inherited: bool = False,
|
|
125
|
+
) -> dict[str, str]:
|
|
126
|
+
"""Controls how response is inserted.
|
|
127
|
+
|
|
128
|
+
https://four.htmx.org/reference/attributes/hx-swap
|
|
129
|
+
"""
|
|
130
|
+
text: str = style
|
|
131
|
+
if transition is not None:
|
|
132
|
+
text += f" transition:{str(transition).lower()}"
|
|
133
|
+
if swap:
|
|
134
|
+
text += f" swap:{swap}"
|
|
135
|
+
if settle:
|
|
136
|
+
text += f" settle:{settle}"
|
|
137
|
+
if ignoreTitle is not None:
|
|
138
|
+
text += f" ignoreTitle:{str(ignoreTitle).lower()}"
|
|
139
|
+
if scroll:
|
|
140
|
+
text += f" scroll:{scroll}"
|
|
141
|
+
if scrollTarget:
|
|
142
|
+
text += f" scrollTarget:{scrollTarget}"
|
|
143
|
+
if show:
|
|
144
|
+
text += f" show:{show}"
|
|
145
|
+
if showTarget:
|
|
146
|
+
text += f" showTarget:{showTarget}"
|
|
147
|
+
if focusScroll is not None:
|
|
148
|
+
text += f" focusScroll:{str(focusScroll).lower()}"
|
|
149
|
+
if target:
|
|
150
|
+
text += f" target:{target}"
|
|
151
|
+
if strip is not None:
|
|
152
|
+
text += f" strip:{str(strip).lower()}"
|
|
153
|
+
if swapEmpty is not None:
|
|
154
|
+
text += f" swapEmpty:{str(swapEmpty).lower()}"
|
|
155
|
+
return {f"hx-swap{':inherited' if inherited else ''}": text}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def morph_skip(*, inherited: bool = False) -> dict[str, bool]:
|
|
159
|
+
"""Skip morphing with attributes and children"""
|
|
160
|
+
return {f"hx-morph-skip{':inherited' if inherited else ''}": True}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def morph_skip_children(*, inherited: bool = False) -> dict[str, bool]:
|
|
164
|
+
"""Skip morphing with children only; attributes still morph"""
|
|
165
|
+
return {f"hx-morph-skip-children{':inherited' if inherited else ''}": True}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def target(selector: _selector, *, inherited: bool = False) -> dict[str, str]:
|
|
169
|
+
"""Controls where response is inserted.
|
|
170
|
+
|
|
171
|
+
https://four.htmx.org/reference/attributes/hx-target
|
|
172
|
+
"""
|
|
173
|
+
return {f"hx-target{':inherited' if inherited else ''}": selector}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def select(selector: _selector, *, inherited: bool = False) -> dict[str, str]:
|
|
177
|
+
"""Controls which response part is inserted.
|
|
178
|
+
|
|
179
|
+
https://four.htmx.org/reference/attributes/hx-select
|
|
180
|
+
"""
|
|
181
|
+
return {f"hx-select{':inherited' if inherited else ''}": selector}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def on(
|
|
185
|
+
event: _event | _htmx_events = "", js: str = "", *, inherited: bool = False
|
|
186
|
+
) -> dict[str, str]:
|
|
187
|
+
"""Runs inline JavaScript when event fires.
|
|
188
|
+
|
|
189
|
+
https://four.htmx.org/reference/attributes/hx-on
|
|
190
|
+
"""
|
|
191
|
+
return {f"hx-on{':' + event if event else ''}{':inherited' if inherited else ''}": js}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class JS:
|
|
195
|
+
"""Used to mark strings as js expressions for use in hx-vals and hx-headers."""
|
|
196
|
+
|
|
197
|
+
start = "HXJS-start:"
|
|
198
|
+
end = ":HXJS-end"
|
|
199
|
+
|
|
200
|
+
def __init__(self, value: str) -> None:
|
|
201
|
+
self.value = value
|
|
202
|
+
|
|
203
|
+
def __repr__(self) -> str:
|
|
204
|
+
return f"{JS.start}{self.value}{JS.end}"
|
|
205
|
+
|
|
206
|
+
def __str__(self) -> str:
|
|
207
|
+
return f"JS({self.value})"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class _HTMXJSONEncoder(json.JSONEncoder):
|
|
211
|
+
def default(self, o: Any) -> Any:
|
|
212
|
+
if isinstance(o, JS):
|
|
213
|
+
return repr(o)
|
|
214
|
+
# Let the base class default method raise the TypeError
|
|
215
|
+
return super().default(o)
|
|
216
|
+
|
|
217
|
+
def encode(self, o: Any) -> str:
|
|
218
|
+
string: str = super().encode(o)
|
|
219
|
+
string = string.replace(f'"{JS.start}', "").replace(f'{JS.end}"', "")
|
|
220
|
+
return string
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def vals(
|
|
224
|
+
data: dict[str, Any], *, js: bool = False, append: bool = False, inherited: bool = False
|
|
225
|
+
) -> dict[str, str]:
|
|
226
|
+
"""Adds values to request parameters.
|
|
227
|
+
|
|
228
|
+
https://four.htmx.org/reference/attributes/hx-vals
|
|
229
|
+
"""
|
|
230
|
+
data_string = json.dumps(data, cls=_HTMXJSONEncoder)
|
|
231
|
+
return {
|
|
232
|
+
f"hx-vals{':append' if append else ''}{':inherited' if inherited else ''}": f"{'js:' if js else ''}{data_string}"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def include(
|
|
237
|
+
selector: _selector, *, append: bool = False, inherited: bool = False
|
|
238
|
+
) -> dict[str, str]:
|
|
239
|
+
"""Includes additional element values in request.
|
|
240
|
+
|
|
241
|
+
https://four.htmx.org/reference/attributes/hx-include
|
|
242
|
+
"""
|
|
243
|
+
return {f"hx-include{':append' if append else ''}{':inherited' if inherited else ''}": selector}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def swap_oob(
|
|
247
|
+
swap_style: _swap_style | bool = True, selector: _selector = "", *, inherited: bool = False
|
|
248
|
+
) -> dict[str, str]:
|
|
249
|
+
"""Marks response elements to swap into page by ID.
|
|
250
|
+
|
|
251
|
+
https://four.htmx.org/reference/attributes/hx-swap-oob
|
|
252
|
+
"""
|
|
253
|
+
if isinstance(swap_style, bool):
|
|
254
|
+
style: str = str(swap_style).lower()
|
|
255
|
+
else:
|
|
256
|
+
style = str(swap_style)
|
|
257
|
+
return {
|
|
258
|
+
f"hx-swap-oob{':inherited' if inherited else ''}": f"{style}{':' + selector if selector else ''}"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def select_oob(*selector: str, inherited: bool = False) -> dict[str, str]:
|
|
263
|
+
"""Picks response elements to swap into page by ID.
|
|
264
|
+
|
|
265
|
+
https://four.htmx.org/reference/attributes/hx-select-oob
|
|
266
|
+
"""
|
|
267
|
+
return {f"hx-select-oob{':inherited' if inherited else ''}": ", ".join(selector)}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def push_url(push: bool = True, *, inherited: bool = False) -> dict[str, str]:
|
|
271
|
+
"""Pushes URL into browser history.
|
|
272
|
+
|
|
273
|
+
https://four.htmx.org/reference/attributes/hx-push-url
|
|
274
|
+
"""
|
|
275
|
+
return {f"hx-push-url{':inherited' if inherited else ''}": str(push).lower()}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def replace_url(replace: bool = True, *, inherited: bool = False) -> dict[str, str]:
|
|
279
|
+
"""Replaces current URL in browser history.
|
|
280
|
+
|
|
281
|
+
https://four.htmx.org/reference/attributes/hx-replace-url
|
|
282
|
+
"""
|
|
283
|
+
return {f"hx-replace-url{':inherited' if inherited else ''}": str(replace).lower()}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def headers(data: dict[str, Any], *, js: bool = False, inherited: bool = False) -> dict[str, str]:
|
|
287
|
+
"""Adds custom headers to request.
|
|
288
|
+
|
|
289
|
+
https://four.htmx.org/reference/attributes/hx-headers
|
|
290
|
+
"""
|
|
291
|
+
return {
|
|
292
|
+
f"hx-headers{':inherited' if inherited else ''}": f"{'js:' if js else ''}{json.dumps(data, cls=_HTMXJSONEncoder)}"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def encoding(
|
|
297
|
+
encoding_string: str | Literal["application/x-www-form-urlencoded", "multipart/form-data"],
|
|
298
|
+
*,
|
|
299
|
+
inherited: bool = False,
|
|
300
|
+
) -> dict[str, str]:
|
|
301
|
+
"""Sets request encoding type.
|
|
302
|
+
|
|
303
|
+
https://four.htmx.org/reference/attributes/hx-encoding
|
|
304
|
+
"""
|
|
305
|
+
return {f"hx-encoding{':inherited' if inherited else ''}": encoding_string}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def indicator(
|
|
309
|
+
selector: _selector, *, append: bool = False, inherited: bool = False
|
|
310
|
+
) -> dict[str, str]:
|
|
311
|
+
"""Specifies loading indicator element.
|
|
312
|
+
|
|
313
|
+
https://four.htmx.org/reference/attributes/hx-indicator
|
|
314
|
+
"""
|
|
315
|
+
return {
|
|
316
|
+
f"hx-indicator{':append' if append else ''}{':inherited' if inherited else ''}": selector
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@overload
|
|
321
|
+
def boost(boost: bool, *, inherited: bool = False) -> dict[str, str]:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@overload
|
|
326
|
+
def boost(
|
|
327
|
+
*,
|
|
328
|
+
swap: _swap_style | str = "",
|
|
329
|
+
target: _selector = "",
|
|
330
|
+
select: _selector = "",
|
|
331
|
+
inherited: bool = False,
|
|
332
|
+
) -> dict[str, str]:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def boost(
|
|
337
|
+
boost: bool | None = None,
|
|
338
|
+
*,
|
|
339
|
+
swap: _swap_style | str = "",
|
|
340
|
+
target: _selector = "",
|
|
341
|
+
select: _selector = "",
|
|
342
|
+
inherited: bool = False,
|
|
343
|
+
) -> dict[str, str]:
|
|
344
|
+
"""Converts links and forms to AJAX.
|
|
345
|
+
|
|
346
|
+
https://four.htmx.org/reference/attributes/hx-boost
|
|
347
|
+
"""
|
|
348
|
+
text = ""
|
|
349
|
+
if swap:
|
|
350
|
+
text += f"swap:{swap}"
|
|
351
|
+
if target:
|
|
352
|
+
text += f" target:{target}"
|
|
353
|
+
if select:
|
|
354
|
+
text += f" select:{select}"
|
|
355
|
+
if boost is not None and text == "":
|
|
356
|
+
text = str(boost).lower()
|
|
357
|
+
return {f"hx-boost{':inherited' if inherited else ''}": text}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def sync(
|
|
361
|
+
selector: _selector,
|
|
362
|
+
strategy: Literal["drop", "abort", "replace", "queue first", "queue last", "queue all"],
|
|
363
|
+
*,
|
|
364
|
+
inherited: bool = False,
|
|
365
|
+
) -> dict[str, str]:
|
|
366
|
+
"""Synchronizes requests between elements.
|
|
367
|
+
|
|
368
|
+
https://four.htmx.org/reference/attributes/hx-sync
|
|
369
|
+
"""
|
|
370
|
+
return {f"hx-sync{':inherited' if inherited else ''}": f"{selector}:{strategy}"}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def confirm(confirmation_prompt: str, *, inherited: bool = False) -> dict[str, str]:
|
|
374
|
+
"""Shows confirmation dialog before request.
|
|
375
|
+
|
|
376
|
+
https://four.htmx.org/reference/attributes/hx-confirm
|
|
377
|
+
"""
|
|
378
|
+
return {f"hx-confirm{':inherited' if inherited else ''}": confirmation_prompt}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def validate(validate: bool = True, *, inherited: bool = False) -> dict[str, str]:
|
|
382
|
+
"""Validates before submitting request.
|
|
383
|
+
|
|
384
|
+
https://four.htmx.org/reference/attributes/hx-validate
|
|
385
|
+
"""
|
|
386
|
+
return {f"hx-validate{':inherited' if inherited else ''}": str(validate).lower()}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def disable(*selector: _selector, merge: bool = False, inherited: bool = False) -> dict[str, str]:
|
|
390
|
+
"""Disables elements during request.
|
|
391
|
+
|
|
392
|
+
https://four.htmx.org/reference/attributes/hx-disable
|
|
393
|
+
"""
|
|
394
|
+
return {
|
|
395
|
+
f"hx-disable{':merge' if merge else ''}{':inherited' if inherited else ''}": ", ".join(
|
|
396
|
+
selector
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def ignore(*, inherited: bool = False) -> dict[str, str]:
|
|
402
|
+
"""Disables htmx processing for element.
|
|
403
|
+
|
|
404
|
+
https://four.htmx.org/reference/attributes/hx-ignore
|
|
405
|
+
"""
|
|
406
|
+
return {f"hx-ignore{':inherited' if inherited else ''}": ""}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def preserve(*, inherited: bool = False) -> dict[str, str]:
|
|
410
|
+
"""Preserves element during swaps.
|
|
411
|
+
|
|
412
|
+
https://four.htmx.org/reference/attributes/hx-preserve
|
|
413
|
+
"""
|
|
414
|
+
return {f"hx-preserve{':inherited' if inherited else ''}": "true"}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def status(
|
|
418
|
+
status_code: str | int,
|
|
419
|
+
*,
|
|
420
|
+
swap: _swap_style | None = None,
|
|
421
|
+
target: _selector = "",
|
|
422
|
+
select: _selector = "",
|
|
423
|
+
push: bool | str = "",
|
|
424
|
+
replace: bool | str = "",
|
|
425
|
+
transition: bool | None = None,
|
|
426
|
+
inherited: bool = False,
|
|
427
|
+
) -> dict[str, str]:
|
|
428
|
+
"""Handles responses differently by status code.
|
|
429
|
+
|
|
430
|
+
https://four.htmx.org/reference/attributes/hx-status
|
|
431
|
+
"""
|
|
432
|
+
text: str = ""
|
|
433
|
+
if swap is not None:
|
|
434
|
+
text += f"swap:{swap}"
|
|
435
|
+
if target:
|
|
436
|
+
text += f" target:{target}"
|
|
437
|
+
if select:
|
|
438
|
+
text += f" select:{select}"
|
|
439
|
+
if push != "":
|
|
440
|
+
text += f" push:{push}"
|
|
441
|
+
if replace != "":
|
|
442
|
+
text += f" replace:{replace}"
|
|
443
|
+
if transition is not None:
|
|
444
|
+
text += f" transition:{transition}"
|
|
445
|
+
return {f"hx-status:{status_code}{':inherited' if inherited else ''}": text}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def action(url: str, *, inherited: bool = False) -> dict[str, str]:
|
|
449
|
+
"""Specifies URL to receive request.
|
|
450
|
+
|
|
451
|
+
https://four.htmx.org/reference/attributes/hx-action
|
|
452
|
+
"""
|
|
453
|
+
return {f"hx-action{':inherited' if inherited else ''}": url}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def method(
|
|
457
|
+
text: Literal["get", "post", "put", "patch", "delete"], *, inherited: bool = False
|
|
458
|
+
) -> dict[str, str]:
|
|
459
|
+
"""Specifies HTTP method for request.
|
|
460
|
+
|
|
461
|
+
https://four.htmx.org/reference/attributes/hx-method
|
|
462
|
+
"""
|
|
463
|
+
return {f"hx-method{':inherited' if inherited else ''}": text}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def config(
|
|
467
|
+
*,
|
|
468
|
+
timeout: int | None = None,
|
|
469
|
+
credentials: str = "",
|
|
470
|
+
cache: str = "",
|
|
471
|
+
redirect: str = "",
|
|
472
|
+
referrer: str = "",
|
|
473
|
+
integrity: str = "",
|
|
474
|
+
validate: bool | None = None,
|
|
475
|
+
append: bool = False,
|
|
476
|
+
inherited: bool = False,
|
|
477
|
+
) -> dict[str, str]:
|
|
478
|
+
"""Configures request behavior.
|
|
479
|
+
|
|
480
|
+
https://four.htmx.org/reference/attributes/hx-config
|
|
481
|
+
"""
|
|
482
|
+
data: dict[str, int | str | bool] = {}
|
|
483
|
+
if timeout is not None:
|
|
484
|
+
data["timeout"] = timeout
|
|
485
|
+
if credentials:
|
|
486
|
+
data["credentials"] = credentials
|
|
487
|
+
if cache:
|
|
488
|
+
data["cache"] = cache
|
|
489
|
+
if redirect:
|
|
490
|
+
data["redirect"] = redirect
|
|
491
|
+
if referrer:
|
|
492
|
+
data["referrer"] = referrer
|
|
493
|
+
if integrity:
|
|
494
|
+
data["integrity"] = integrity
|
|
495
|
+
if validate is not None:
|
|
496
|
+
data["validate"] = validate
|
|
497
|
+
return {
|
|
498
|
+
f"hx-config{':append' if append else ''}{':inherited' if inherited else ''}": json.dumps(
|
|
499
|
+
data
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def history_elt(*, inherited: bool = False) -> dict[str, str]:
|
|
505
|
+
"""Marks element to swap on history restore.
|
|
506
|
+
|
|
507
|
+
https://four.htmx.org/reference/attributes/hx-history-elt
|
|
508
|
+
"""
|
|
509
|
+
return {f"hx-history-elt{':inherited' if inherited else ''}": ""}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: htpy-htmx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Helpers for using htmx in htpy
|
|
5
|
+
Author-email: August Malmdahl <august.malmdahl@gmail.com>
|
|
6
|
+
Project-URL: Repository, https://github.com/augustifolia/htpy-htmx
|
|
7
|
+
Project-URL: Issues, https://github.com/augustifolia/htpy-htmx/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENCE
|
|
17
|
+
Requires-Dist: htpy
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# htpy-htmx
|
|
21
|
+
|
|
22
|
+
htpy-htmx is a library to make it easier to work with [htmx](https://four.htmx.org/) in [htpy](https://htpy.dev/).
|
|
23
|
+
|
|
24
|
+
This library is intended to work with htmx 4.
|
|
25
|
+
|
|
26
|
+
Note: This project is still a work in progress and there may be breaking changes.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
You can import htpy-htmx as htpy_htmx.hx. All normal htmx attributes are available from the module,
|
|
31
|
+
but without the "hx-" prefix. So `hx-get="url"` would become `hx.get("url")`.
|
|
32
|
+
|
|
33
|
+
Some htmx extension attributes are available in the htpy_htmx.ext module.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import htpy as h
|
|
37
|
+
from htpy_htmx import hx
|
|
38
|
+
from htpy_htmx import ext
|
|
39
|
+
|
|
40
|
+
# pass htmx attributes as functions
|
|
41
|
+
h.div("#div-id", hx.get("/some-url/"), hx.trigger("click"))
|
|
42
|
+
|
|
43
|
+
# hx-partial is also available as a htpy element
|
|
44
|
+
h.div("#div-id")[
|
|
45
|
+
h.h2["header"],
|
|
46
|
+
hx.partial("#part-id", hx.target("#some-element"))[
|
|
47
|
+
h.div("#div1")[
|
|
48
|
+
h.p["part one"]
|
|
49
|
+
]
|
|
50
|
+
],
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
h.div(
|
|
54
|
+
# trigger allows a list of trigger events
|
|
55
|
+
hx.trigger("blur", "keyup[key=='Enter']"),
|
|
56
|
+
# swap takes a swap style as string and modifiers as keyword arguments
|
|
57
|
+
hx.swap("before", swap="1s", scroll="bottom"),
|
|
58
|
+
# hx-on takes an event and a JavaScript expression
|
|
59
|
+
hx.on("htmx:before:request", "showSpinner()"),
|
|
60
|
+
# vals takes a dict of data.
|
|
61
|
+
# To evaluate JavaScript, wrap your js expression in hx.JS() and pass js=True.
|
|
62
|
+
hx.vals({"foo": "bar", "some_js": hx.JS("getSomeData()")}, js=True),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# to enable inheritance pass inherited=True to a hx-function
|
|
66
|
+
h.div(hx.confirm("Are you sure?", inherited=True))[
|
|
67
|
+
h.button(hx.delete("/account"))["Delete My Account"],
|
|
68
|
+
h.button(hx.put("/account"))["Update My Account"],
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# Some extension attributes are available in ext:
|
|
72
|
+
h.div(
|
|
73
|
+
ext.preload("touchstart"),
|
|
74
|
+
ext.optimistic("#msg-opt"),
|
|
75
|
+
ext.ws(connect="click"),
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
To install with pip run:
|
|
82
|
+
|
|
83
|
+
pip install htpy-htmx
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
htpy_htmx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
htpy_htmx/_types.py,sha256=VNFk6T8nDIvQ9QEiEQbGCo-lGvq1wzBUcIIu37d-uMk,2351
|
|
3
|
+
htpy_htmx/ext.py,sha256=ORI6uXsskt5LqWZXW4BYma8fwjKpFi6OjOBThHRvnM8,4351
|
|
4
|
+
htpy_htmx/hx.py,sha256=HaimQdXfOWa73yl5lIcUUoGFksKmRn6rlGd_u1uxgow,14787
|
|
5
|
+
htpy_htmx-0.1.0.dist-info/licenses/LICENCE,sha256=Vl3Hxc8G6RHH3q4lgrNKZuJiVQD3FOdwGbUAkJvN1Jc,1072
|
|
6
|
+
htpy_htmx-0.1.0.dist-info/METADATA,sha256=9NcFHrSbGSDYOSWRUTIi6406UiI_erf70IraWiOd95Y,2571
|
|
7
|
+
htpy_htmx-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
htpy_htmx-0.1.0.dist-info/top_level.txt,sha256=xKrtpdM7_Rv-NIPYrfAV1qHatgmJuFkuzu4LEHN4Twc,10
|
|
9
|
+
htpy_htmx-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 August Malmdahl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
htpy_htmx
|