djhtmx 1.2.8__py3-none-any.whl → 1.2.9__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.
- djhtmx/__init__.py +1 -1
- djhtmx/command_queue.py +11 -1
- djhtmx/component.py +51 -9
- djhtmx/consumer.py +2 -1
- djhtmx/repo.py +11 -1
- djhtmx/static/htmx/django.js +218 -194
- djhtmx/testing.py +10 -2
- djhtmx/urls.py +15 -1
- djhtmx/utils.py +17 -14
- {djhtmx-1.2.8.dist-info → djhtmx-1.2.9.dist-info}/METADATA +1 -1
- {djhtmx-1.2.8.dist-info → djhtmx-1.2.9.dist-info}/RECORD +13 -13
- {djhtmx-1.2.8.dist-info → djhtmx-1.2.9.dist-info}/WHEEL +0 -0
- {djhtmx-1.2.8.dist-info → djhtmx-1.2.9.dist-info}/licenses/LICENSE +0 -0
djhtmx/__init__.py
CHANGED
djhtmx/command_queue.py
CHANGED
|
@@ -16,6 +16,7 @@ from .component import (
|
|
|
16
16
|
Open,
|
|
17
17
|
Redirect,
|
|
18
18
|
Render,
|
|
19
|
+
ScrollIntoView,
|
|
19
20
|
Signal,
|
|
20
21
|
SkipRender,
|
|
21
22
|
)
|
|
@@ -86,6 +87,7 @@ class CommandQueue:
|
|
|
86
87
|
| Emit()
|
|
87
88
|
| SkipRender()
|
|
88
89
|
| Focus()
|
|
90
|
+
| ScrollIntoView()
|
|
89
91
|
| Redirect()
|
|
90
92
|
| DispatchDOMEvent()
|
|
91
93
|
| Open()
|
|
@@ -139,7 +141,15 @@ class CommandQueue:
|
|
|
139
141
|
return 6, component.id, timestamp
|
|
140
142
|
else:
|
|
141
143
|
return 7, component.id, timestamp
|
|
142
|
-
case
|
|
144
|
+
case (
|
|
145
|
+
Focus()
|
|
146
|
+
| ScrollIntoView()
|
|
147
|
+
| Redirect()
|
|
148
|
+
| DispatchDOMEvent()
|
|
149
|
+
| Open()
|
|
150
|
+
| ReplaceURL()
|
|
151
|
+
| PushURL()
|
|
152
|
+
):
|
|
143
153
|
return 8, "", 0
|
|
144
154
|
case _ as unreachable:
|
|
145
155
|
assert_never(unreachable)
|
djhtmx/component.py
CHANGED
|
@@ -89,6 +89,16 @@ class Focus:
|
|
|
89
89
|
command: Literal["focus"] = "focus"
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
@dataclass(slots=True)
|
|
93
|
+
class ScrollIntoView:
|
|
94
|
+
"Scrolls the browser element that matches `selector` into view"
|
|
95
|
+
|
|
96
|
+
selector: str
|
|
97
|
+
behavior: Literal["auto", "smooth", "instant"] = "smooth"
|
|
98
|
+
block: Literal["start", "center", "end", "nearest"] = "center"
|
|
99
|
+
command: Literal["scroll_into_view"] = "scroll_into_view"
|
|
100
|
+
|
|
101
|
+
|
|
92
102
|
@dataclass(slots=True)
|
|
93
103
|
class Execute:
|
|
94
104
|
component_id: str
|
|
@@ -126,34 +136,62 @@ class BuildAndRender:
|
|
|
126
136
|
|
|
127
137
|
@classmethod
|
|
128
138
|
def append(
|
|
129
|
-
cls,
|
|
139
|
+
cls,
|
|
140
|
+
target_: str,
|
|
141
|
+
component_: type[HtmxComponent],
|
|
142
|
+
parent_id: str | None = None,
|
|
143
|
+
**state,
|
|
130
144
|
):
|
|
131
145
|
return cls(
|
|
132
|
-
component=component_,
|
|
146
|
+
component=component_,
|
|
147
|
+
state=state,
|
|
148
|
+
oob=f"beforeend: {target_}",
|
|
149
|
+
parent_id=parent_id,
|
|
133
150
|
)
|
|
134
151
|
|
|
135
152
|
@classmethod
|
|
136
153
|
def prepend(
|
|
137
|
-
cls,
|
|
154
|
+
cls,
|
|
155
|
+
target_: str,
|
|
156
|
+
component_: type[HtmxComponent],
|
|
157
|
+
parent_id: str | None = None,
|
|
158
|
+
**state,
|
|
138
159
|
):
|
|
139
160
|
return cls(
|
|
140
|
-
component=component_,
|
|
161
|
+
component=component_,
|
|
162
|
+
state=state,
|
|
163
|
+
oob=f"afterbegin: {target_}",
|
|
164
|
+
parent_id=parent_id,
|
|
141
165
|
)
|
|
142
166
|
|
|
143
167
|
@classmethod
|
|
144
168
|
def after(
|
|
145
|
-
cls,
|
|
169
|
+
cls,
|
|
170
|
+
target_: str,
|
|
171
|
+
component_: type[HtmxComponent],
|
|
172
|
+
parent_id: str | None = None,
|
|
173
|
+
**state,
|
|
146
174
|
):
|
|
147
175
|
return cls(
|
|
148
|
-
component=component_,
|
|
176
|
+
component=component_,
|
|
177
|
+
state=state,
|
|
178
|
+
oob=f"afterend: {target_}",
|
|
179
|
+
parent_id=parent_id,
|
|
149
180
|
)
|
|
150
181
|
|
|
151
182
|
@classmethod
|
|
152
183
|
def before(
|
|
153
|
-
cls,
|
|
184
|
+
cls,
|
|
185
|
+
target_: str,
|
|
186
|
+
component_: type[HtmxComponent],
|
|
187
|
+
parent_id: str | None = None,
|
|
188
|
+
**state,
|
|
154
189
|
):
|
|
155
190
|
return cls(
|
|
156
|
-
component=component_,
|
|
191
|
+
component=component_,
|
|
192
|
+
state=state,
|
|
193
|
+
oob=f"beforebegin: {target_}",
|
|
194
|
+
parent_id=parent_id,
|
|
157
195
|
)
|
|
158
196
|
|
|
159
197
|
@classmethod
|
|
@@ -191,6 +229,7 @@ Command = (
|
|
|
191
229
|
Destroy
|
|
192
230
|
| Redirect
|
|
193
231
|
| Focus
|
|
232
|
+
| ScrollIntoView
|
|
194
233
|
| DispatchDOMEvent
|
|
195
234
|
| SkipRender
|
|
196
235
|
| BuildAndRender
|
|
@@ -249,7 +288,10 @@ def get_template(template: str) -> RenderFunction: # pragma: no cover
|
|
|
249
288
|
return cast(RenderFunction, _compose(loader.get_template(template).render, mark_safe))
|
|
250
289
|
else:
|
|
251
290
|
if (render := RENDER_FUNC.get(template)) is None:
|
|
252
|
-
render = cast(
|
|
291
|
+
render = cast(
|
|
292
|
+
RenderFunction,
|
|
293
|
+
_compose(loader.get_template(template).render, mark_safe),
|
|
294
|
+
)
|
|
253
295
|
RENDER_FUNC[template] = render
|
|
254
296
|
return render
|
|
255
297
|
|
djhtmx/consumer.py
CHANGED
|
@@ -6,7 +6,7 @@ from pydantic import BaseModel, TypeAdapter
|
|
|
6
6
|
|
|
7
7
|
from . import json
|
|
8
8
|
from .commands import PushURL, ReplaceURL, SendHtml
|
|
9
|
-
from .component import Command, Destroy, DispatchDOMEvent, Focus, Open, Redirect
|
|
9
|
+
from .component import Command, Destroy, DispatchDOMEvent, Focus, Open, Redirect, ScrollIntoView
|
|
10
10
|
from .introspection import parse_request_data
|
|
11
11
|
from .repo import Repository
|
|
12
12
|
from .utils import get_params
|
|
@@ -58,6 +58,7 @@ class Consumer(AsyncJsonWebsocketConsumer):
|
|
|
58
58
|
Destroy()
|
|
59
59
|
| Redirect()
|
|
60
60
|
| Focus()
|
|
61
|
+
| ScrollIntoView()
|
|
61
62
|
| DispatchDOMEvent()
|
|
62
63
|
| PushURL()
|
|
63
64
|
| Open()
|
djhtmx/repo.py
CHANGED
|
@@ -35,6 +35,7 @@ from .component import (
|
|
|
35
35
|
Open,
|
|
36
36
|
Redirect,
|
|
37
37
|
Render,
|
|
38
|
+
ScrollIntoView,
|
|
38
39
|
Signal,
|
|
39
40
|
SkipRender,
|
|
40
41
|
_get_query_patchers,
|
|
@@ -56,7 +57,15 @@ logger = logging.getLogger(__name__)
|
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
ProcessedCommand = (
|
|
59
|
-
Destroy
|
|
60
|
+
Destroy
|
|
61
|
+
| Redirect
|
|
62
|
+
| Open
|
|
63
|
+
| Focus
|
|
64
|
+
| ScrollIntoView
|
|
65
|
+
| DispatchDOMEvent
|
|
66
|
+
| SendHtml
|
|
67
|
+
| PushURL
|
|
68
|
+
| ReplaceURL
|
|
60
69
|
)
|
|
61
70
|
|
|
62
71
|
|
|
@@ -296,6 +305,7 @@ class Repository:
|
|
|
296
305
|
| PushURL()
|
|
297
306
|
| Redirect()
|
|
298
307
|
| Focus()
|
|
308
|
+
| ScrollIntoView()
|
|
299
309
|
| DispatchDOMEvent() as command
|
|
300
310
|
):
|
|
301
311
|
commands.processing_component_id = ""
|
djhtmx/static/htmx/django.js
CHANGED
|
@@ -1,197 +1,221 @@
|
|
|
1
|
-
(
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
1
|
+
(() => {
|
|
2
|
+
// WebSocket Management
|
|
3
|
+
const sentComponents = new Set();
|
|
4
|
+
|
|
5
|
+
function sendRemovedComponents(event) {
|
|
6
|
+
const removedComponents = Array.from(sentComponents).filter(
|
|
7
|
+
(id) => !document.getElementById(id),
|
|
8
|
+
);
|
|
9
|
+
for (const id of removedComponents) {
|
|
10
|
+
sentComponents.delete(id);
|
|
11
|
+
}
|
|
12
|
+
if (removedComponents.length) {
|
|
13
|
+
event.detail.socketWrapper.send(
|
|
14
|
+
JSON.stringify({
|
|
15
|
+
type: "removed",
|
|
16
|
+
component_ids: removedComponents,
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sendAddedComponents(event) {
|
|
23
|
+
const states = [];
|
|
24
|
+
const subscriptions = new Map();
|
|
25
|
+
const ids = new Set();
|
|
26
|
+
|
|
27
|
+
for (const element of Array.from(
|
|
28
|
+
document.querySelectorAll("[data-hx-state]"),
|
|
29
|
+
).filter((el) => !sentComponents.has(el.id))) {
|
|
30
|
+
const hxSubscriptions = element.dataset.hxSubscriptions;
|
|
31
|
+
if (hxSubscriptions !== undefined) {
|
|
32
|
+
subscriptions[element.id] = element.dataset.hxSubscriptions;
|
|
33
|
+
}
|
|
34
|
+
states.push(element.dataset.hxState);
|
|
35
|
+
ids.add(element.id);
|
|
36
|
+
}
|
|
37
|
+
for (const id of ids) {
|
|
38
|
+
sentComponents.add(id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (ids.size) {
|
|
42
|
+
event.detail.socketWrapper.send(
|
|
43
|
+
JSON.stringify({
|
|
44
|
+
type: "added",
|
|
45
|
+
states,
|
|
46
|
+
subscriptions,
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function removeHtmxIndicator() {
|
|
53
|
+
// remove indicator
|
|
54
|
+
for (const el of document.querySelectorAll(".htmx-request")) {
|
|
55
|
+
el.classList.remove("htmx-request");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
document.addEventListener("htmx:wsOpen", (event) => {
|
|
60
|
+
console.log("OPEN", event);
|
|
61
|
+
sentComponents.clear();
|
|
62
|
+
removeHtmxIndicator();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
document.addEventListener("htmx:wsClose", (event) => {
|
|
66
|
+
console.log("CLOSE", event);
|
|
67
|
+
sentComponents.clear();
|
|
68
|
+
removeHtmxIndicator();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
document.addEventListener("htmx:wsConfigSend", (event) => {
|
|
72
|
+
// add indicator
|
|
73
|
+
const indicatorSelector = event.detail.elt
|
|
74
|
+
.closest("[hx-indicator]")
|
|
75
|
+
?.getAttribute("hx-indicator");
|
|
76
|
+
if (indicatorSelector) {
|
|
77
|
+
for (const el of document.querySelectorAll(indicatorSelector)) {
|
|
78
|
+
el.classList.add("htmx-request");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// send current state
|
|
83
|
+
sendRemovedComponents(event);
|
|
84
|
+
sendAddedComponents(event);
|
|
85
|
+
|
|
86
|
+
// enrich event message
|
|
87
|
+
event.detail.headers["HX-Component-Id"] =
|
|
88
|
+
event.detail.elt.closest("[data-hx-state]").id;
|
|
89
|
+
event.detail.headers["HX-Component-Handler"] =
|
|
90
|
+
event.detail.elt.getAttribute("ws-send");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
document.addEventListener("htmx:wsBeforeMessage", (event) => {
|
|
94
|
+
removeHtmxIndicator();
|
|
95
|
+
|
|
96
|
+
// process message
|
|
97
|
+
if (event.detail.message.startsWith("{")) {
|
|
98
|
+
const commandData = JSON.parse(event.detail.message);
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
const { command } = commandData;
|
|
101
|
+
switch (command) {
|
|
102
|
+
case "destroy": {
|
|
103
|
+
const { component_id } = commandData;
|
|
104
|
+
document.getElementById(component_id)?.remove();
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "focus": {
|
|
108
|
+
const { selector } = commandData;
|
|
109
|
+
document.querySelector(selector)?.focus();
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "scroll_into_view": {
|
|
113
|
+
const { selector, behavior = "smooth", block = "center" } = commandData;
|
|
114
|
+
document.querySelector(selector)?.scrollIntoView({
|
|
115
|
+
behavior,
|
|
116
|
+
block,
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "redirect": {
|
|
121
|
+
const { url } = commandData;
|
|
122
|
+
location.assign(url);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "dispatch_event": {
|
|
126
|
+
const { target, detail, buubles, cancelable, composed } = commandData;
|
|
127
|
+
document.querySelector(target)?.dispatchEvent(
|
|
128
|
+
new CustomEvent(event, {
|
|
129
|
+
detail,
|
|
130
|
+
buubles,
|
|
131
|
+
cancelable,
|
|
132
|
+
composed,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "send_state": {
|
|
138
|
+
const { component_id, state } = commandData;
|
|
139
|
+
const component = document.getElementById(component_id);
|
|
140
|
+
if (component) {
|
|
141
|
+
component.dataset.hxState = state;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "push_url": {
|
|
146
|
+
const { url } = commandData;
|
|
147
|
+
history.pushState({}, document.title, url);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
console.error("Can't process command:", event.detail.message);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
document.addEventListener("hxDispatchDOMEvent", (event) => {
|
|
159
|
+
for (const {
|
|
160
|
+
event: eventName,
|
|
161
|
+
target,
|
|
162
|
+
detail,
|
|
163
|
+
bubbles,
|
|
164
|
+
cancelable,
|
|
165
|
+
composed,
|
|
166
|
+
} of event.detail.value) {
|
|
167
|
+
const el = document.querySelector(target);
|
|
168
|
+
if (el) {
|
|
169
|
+
// This setTimeout basically queues the dispatch of the event
|
|
170
|
+
// to avoid dispatching events within events handlers.
|
|
171
|
+
setTimeout(
|
|
172
|
+
() =>
|
|
173
|
+
el.dispatchEvent(
|
|
174
|
+
new CustomEvent(eventName, {
|
|
175
|
+
detail,
|
|
176
|
+
bubbles,
|
|
177
|
+
cancelable,
|
|
178
|
+
composed,
|
|
179
|
+
}),
|
|
180
|
+
),
|
|
181
|
+
0,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
document.addEventListener("hxFocus", (event) => {
|
|
188
|
+
for (const selector of event.detail.value) {
|
|
189
|
+
document.querySelector(selector).focus();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
document.addEventListener("hxScrollIntoView", (event) => {
|
|
194
|
+
for (const item of event.detail.value) {
|
|
195
|
+
const selector = typeof item === "string" ? item : item.selector;
|
|
196
|
+
const behavior = typeof item === "object" ? item.behavior || "smooth" : "smooth";
|
|
197
|
+
const block = typeof item === "object" ? item.block || "center" : "center";
|
|
198
|
+
document.querySelector(selector)?.scrollIntoView({
|
|
199
|
+
behavior,
|
|
200
|
+
block,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
document.addEventListener("hxOpenURL", (event) => {
|
|
206
|
+
for (const { url, name, target, rel } of event.detail.value) {
|
|
207
|
+
const link = document.createElement("a");
|
|
208
|
+
link.href = url;
|
|
209
|
+
link.target = target || "_blank";
|
|
210
|
+
if (name) {
|
|
211
|
+
link.download = name;
|
|
212
|
+
}
|
|
213
|
+
if (rel) {
|
|
214
|
+
link.rel = rel;
|
|
215
|
+
}
|
|
216
|
+
link.click();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
195
219
|
})();
|
|
196
220
|
// Local Variables:
|
|
197
221
|
// js-indent-level: 4
|
djhtmx/testing.py
CHANGED
|
@@ -13,7 +13,15 @@ from pygments.lexers import HtmlLexer
|
|
|
13
13
|
|
|
14
14
|
from . import json
|
|
15
15
|
from .commands import PushURL, ReplaceURL, SendHtml
|
|
16
|
-
from .component import
|
|
16
|
+
from .component import (
|
|
17
|
+
Destroy,
|
|
18
|
+
DispatchDOMEvent,
|
|
19
|
+
Focus,
|
|
20
|
+
HtmxComponent,
|
|
21
|
+
Open,
|
|
22
|
+
Redirect,
|
|
23
|
+
ScrollIntoView,
|
|
24
|
+
)
|
|
17
25
|
from .introspection import parse_request_data
|
|
18
26
|
from .repo import Repository, Session, signer
|
|
19
27
|
from .utils import get_params
|
|
@@ -187,7 +195,7 @@ class Htmx:
|
|
|
187
195
|
self.path = parsed_url.path
|
|
188
196
|
self.query_string = parsed_url.query
|
|
189
197
|
|
|
190
|
-
case Focus() | DispatchDOMEvent():
|
|
198
|
+
case Focus() | ScrollIntoView() | DispatchDOMEvent():
|
|
191
199
|
pass
|
|
192
200
|
|
|
193
201
|
if navigate_to_url:
|
djhtmx/urls.py
CHANGED
|
@@ -11,7 +11,16 @@ from django.utils.html import format_html
|
|
|
11
11
|
from django.views.decorators.csrf import csrf_exempt
|
|
12
12
|
|
|
13
13
|
from .commands import PushURL, ReplaceURL, SendHtml
|
|
14
|
-
from .component import
|
|
14
|
+
from .component import (
|
|
15
|
+
REGISTRY,
|
|
16
|
+
Destroy,
|
|
17
|
+
DispatchDOMEvent,
|
|
18
|
+
Focus,
|
|
19
|
+
Open,
|
|
20
|
+
Redirect,
|
|
21
|
+
ScrollIntoView,
|
|
22
|
+
Triggers,
|
|
23
|
+
)
|
|
15
24
|
from .consumer import Consumer
|
|
16
25
|
from .introspection import parse_request_data
|
|
17
26
|
from .repo import Repository
|
|
@@ -53,6 +62,11 @@ def endpoint(request: HttpRequest, component_name: str, component_id: str, event
|
|
|
53
62
|
headers["HX-Redirect"] = url
|
|
54
63
|
case Focus(selector):
|
|
55
64
|
triggers.after_settle("hxFocus", selector)
|
|
65
|
+
case ScrollIntoView(selector, behavior, block):
|
|
66
|
+
triggers.after_settle(
|
|
67
|
+
"hxScrollIntoView",
|
|
68
|
+
{"selector": selector, "behavior": behavior, "block": block},
|
|
69
|
+
)
|
|
56
70
|
case Open(url, name, target, rel):
|
|
57
71
|
triggers.after_settle(
|
|
58
72
|
"hxOpenURL",
|
djhtmx/utils.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import contextlib
|
|
2
1
|
import importlib
|
|
2
|
+
import logging
|
|
3
3
|
import pkgutil
|
|
4
4
|
import typing as t
|
|
5
5
|
from urllib.parse import urlparse
|
|
@@ -129,17 +129,20 @@ def autodiscover_htmx_modules():
|
|
|
129
129
|
- htmx.py files (like standard autodiscover_modules("htmx"))
|
|
130
130
|
- All Python files under htmx/ directories in apps (recursively)
|
|
131
131
|
"""
|
|
132
|
-
|
|
133
|
-
def _import_modules_recursively(module_name):
|
|
134
|
-
"""Recursively import a module and all its submodules."""
|
|
135
|
-
with contextlib.suppress(ImportError):
|
|
136
|
-
module = importlib.import_module(module_name)
|
|
137
|
-
|
|
138
|
-
# If this is a package, recursively import all modules in it
|
|
139
|
-
if hasattr(module, "__path__"):
|
|
140
|
-
for _finder, submodule_name, _is_pkg in pkgutil.iter_modules(module.__path__):
|
|
141
|
-
_import_modules_recursively(f"{module_name}.{submodule_name}")
|
|
142
|
-
|
|
143
132
|
for app_config in apps.get_app_configs():
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
module_name = f"{app_config.module.__name__}.htmx"
|
|
134
|
+
try:
|
|
135
|
+
module = importlib.import_module(module_name)
|
|
136
|
+
except ImportError:
|
|
137
|
+
logger.warning("Could not import %s", module_name)
|
|
138
|
+
continue
|
|
139
|
+
if hasattr(module, "__path__"):
|
|
140
|
+
# If it's a package, recursively walk it importing all modules and packages.
|
|
141
|
+
for info in pkgutil.walk_packages(module.__path__, prefix=module_name + "."):
|
|
142
|
+
if not info.ispkg:
|
|
143
|
+
# `walk_packages` only imports packages, not modules; we need to import them
|
|
144
|
+
# all.
|
|
145
|
+
importlib.import_module(info.name)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
logger = logging.getLogger(__name__)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
djhtmx/__init__.py,sha256=
|
|
1
|
+
djhtmx/__init__.py,sha256=Xo29y2KoMKHDn3Doa4sLhJWnPHI5uv_fhC_zm_8GBb4,84
|
|
2
2
|
djhtmx/apps.py,sha256=hAyjzmInEstxLY9k8Qn58LvNlezgQLx5_NqyVL1WwYs,323
|
|
3
|
-
djhtmx/command_queue.py,sha256=
|
|
3
|
+
djhtmx/command_queue.py,sha256=LSUkb2YMRt1lDyOg6WP7PoHsObynec0B55JyFtcshT0,5090
|
|
4
4
|
djhtmx/commands.py,sha256=UxXbARd4Teetjh_zjvAWgI2KNbvdETH-WrGf4qD9Xr8,1206
|
|
5
|
-
djhtmx/component.py,sha256=
|
|
6
|
-
djhtmx/consumer.py,sha256=
|
|
5
|
+
djhtmx/component.py,sha256=1aw5hAwq8MhQPP4cNCRQg9qO1GONd_KlxyYpcQeRhwg,16661
|
|
6
|
+
djhtmx/consumer.py,sha256=0Yh8urgMH3khA6_pWeY049w3jqHWZL_K9dErOhNctQA,2898
|
|
7
7
|
djhtmx/context.py,sha256=cWvz8Z0MC6x_G8sn5mvoH8Hu38qReY21_eNdThuba1A,214
|
|
8
8
|
djhtmx/exceptions.py,sha256=UtyE1N-52OmzwgRM9xFxjUuhHTMDvD7Oy3rNpgthLcs,47
|
|
9
9
|
djhtmx/global_events.py,sha256=bYb8WmQn_WsZ_Dadr0pGiGOPia01K-VanPpM97Lt324,342
|
|
@@ -11,14 +11,14 @@ djhtmx/introspection.py,sha256=flVolO6xZiXsxMm876ZCEcWRmVfFsJWpAVmIdfcJNf8,13734
|
|
|
11
11
|
djhtmx/json.py,sha256=7cjwWIJj7e0dk54INKYZJe6zKkIW7wlsNSlD05cbXfY,1374
|
|
12
12
|
djhtmx/middleware.py,sha256=JuMtv9ZnpungTvQ1qD2Lg6LiFPB3knQlA1ERgH4iGl0,1274
|
|
13
13
|
djhtmx/query.py,sha256=UyjN1jokh4wTwQJxcRwA9f-Zn-A7A4GLToeGrCnPhKA,6674
|
|
14
|
-
djhtmx/repo.py,sha256=
|
|
14
|
+
djhtmx/repo.py,sha256=Qw8t9i5_upfvYJyUr4Cb9hZoI9WUHhNOpszxjhtx9r4,22478
|
|
15
15
|
djhtmx/settings.py,sha256=Iti4LkcKBTy-dNyCZxFH_cUp56aTcXjB5ftbssWyDnU,1318
|
|
16
|
-
djhtmx/testing.py,sha256=
|
|
16
|
+
djhtmx/testing.py,sha256=QmZHrH6Up8uUxkVOHM6CyfocU774GL-lopJvM2X9Mkw,8369
|
|
17
17
|
djhtmx/tracing.py,sha256=xkCXb7t_3yCj1PGzmQfHPu9sYQftDKwtALaEbFVnQ1E,1260
|
|
18
|
-
djhtmx/urls.py,sha256=
|
|
19
|
-
djhtmx/utils.py,sha256=
|
|
18
|
+
djhtmx/urls.py,sha256=2LTzmBCd3lBlQcM6WrdQwkITxuL_4ArUHtiYbLk3T1M,4273
|
|
19
|
+
djhtmx/utils.py,sha256=LCRdBqdvFjXC5l6pywMq3fm344_KQwt3Y2MW2-Nw3tM,4711
|
|
20
20
|
djhtmx/management/commands/htmx.py,sha256=tEtiJn_Z6byOFzBNIzTbdluA4T5q21zFwGvJ7yt90bw,3642
|
|
21
|
-
djhtmx/static/htmx/django.js,sha256=
|
|
21
|
+
djhtmx/static/htmx/django.js,sha256=QDgkUBiX9PBa_bqfK2k4NwjBAgZlxYEK-JklOgn_IR4,5453
|
|
22
22
|
djhtmx/static/htmx/2.0.4/htmx.amd.js,sha256=Hgmm_X5zw7ek0pjBaxhzH7OHx6Xfce5UYVa9ICWlWR0,165593
|
|
23
23
|
djhtmx/static/htmx/2.0.4/htmx.cjs.js,sha256=4P3vh1eGwULBCT7wsKQ2bu4HiNQ_Kmnv2fP1RQ6_QW8,165586
|
|
24
24
|
djhtmx/static/htmx/2.0.4/htmx.esm.d.ts,sha256=hV0FewMl4vWgZFCX_wIpSATGkXou7o1DXOLnVhvgvJI,7330
|
|
@@ -31,7 +31,7 @@ djhtmx/templates/htmx/headers.html,sha256=z7r9klwBDXDyjbHrzatZeHDvXB2DaZhgu55CFb
|
|
|
31
31
|
djhtmx/templates/htmx/lazy.html,sha256=LfAThtKmFj-lCUZ7JWF_sC1Y6XsIpEz8A3IgWASn-J8,52
|
|
32
32
|
djhtmx/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
djhtmx/templatetags/htmx.py,sha256=-qFqz4T9mCJocG9XIIey81cCYwk07XUd_DMpxNdmbsM,8397
|
|
34
|
-
djhtmx-1.2.
|
|
35
|
-
djhtmx-1.2.
|
|
36
|
-
djhtmx-1.2.
|
|
37
|
-
djhtmx-1.2.
|
|
34
|
+
djhtmx-1.2.9.dist-info/METADATA,sha256=0JFyGGexxkpISm-AWnAlvzbQ0-Ga0RHGaCnmpWjT_XQ,32245
|
|
35
|
+
djhtmx-1.2.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
djhtmx-1.2.9.dist-info/licenses/LICENSE,sha256=kCi_iSBUGsRZInQn96w7LXYzjiRjZ8FXl6vP--mFRPk,1085
|
|
37
|
+
djhtmx-1.2.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|