pywebview-htmx 0.3.1__tar.gz → 0.4.1__tar.gz
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.
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/PKG-INFO +41 -11
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/README.md +40 -10
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/pyproject.toml +22 -19
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/static/runtime.js +54 -21
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/static/themes/base.css +44 -102
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/static/themes/cybermind.css +4 -13
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/tests/conftest.py +0 -7
- pywebview_htmx-0.4.1/tests/test_js_runtime.py +774 -0
- pywebview_htmx-0.4.1/tests/test_repo_legibility.py +125 -0
- pywebview_htmx-0.3.1/tests/js_runtime_behavior_harness.mjs +0 -369
- pywebview_htmx-0.3.1/tests/test_js_contract_config_and_binding.py +0 -76
- pywebview_htmx-0.3.1/tests/test_js_contract_ergonomics.py +0 -125
- pywebview_htmx-0.3.1/tests/test_js_contract_request_and_swap.py +0 -96
- pywebview_htmx-0.3.1/tests/test_js_runtime_behavior.py +0 -27
- pywebview_htmx-0.3.1/tests/test_repo_legibility.py +0 -125
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/LICENSE +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/__init__.py +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/py.typed +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/runtime.py +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/static/themes/aurora.css +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/src/pywebview_htmx/static/themes/paper.css +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/tests/test_python_create_window.py +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/tests/test_python_injection.py +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/tests/test_python_params_attr.py +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/tests/test_python_script_loading.py +0 -0
- {pywebview_htmx-0.3.1 → pywebview_htmx-0.4.1}/tests/test_python_themes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywebview-htmx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: HTMX-style declarative UI bindings for PyWebview apps
|
|
5
5
|
Author-Email: "B.T. Franklin" <brandon.franklin@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -120,12 +120,14 @@ The skill includes:
|
|
|
120
120
|
## Quick Start
|
|
121
121
|
|
|
122
122
|
```python
|
|
123
|
+
from html import escape
|
|
124
|
+
|
|
123
125
|
from pywebview_htmx import create_window
|
|
124
126
|
|
|
125
127
|
|
|
126
128
|
class API:
|
|
127
129
|
def greeting(self, params: dict) -> str:
|
|
128
|
-
name = params.get("name", "world")
|
|
130
|
+
name = escape(str(params.get("name", "world")))
|
|
129
131
|
return f"<p>Hello, {name}!</p>"
|
|
130
132
|
|
|
131
133
|
|
|
@@ -229,6 +231,12 @@ DOM event name to bind. Default is `click`.
|
|
|
229
231
|
<div py-call="show_tip" py-trigger="mouseenter">...</div>
|
|
230
232
|
```
|
|
231
233
|
|
|
234
|
+
When the runtime handles a native action that would leave or destructively
|
|
235
|
+
change the current document, it prevents that default action. This includes
|
|
236
|
+
form submission, link navigation, and submit/reset controls associated with a
|
|
237
|
+
form. Ordinary buttons and the native state changes of checkboxes, radio
|
|
238
|
+
buttons, and labels are preserved.
|
|
239
|
+
|
|
232
240
|
### `data-py-params` (optional)
|
|
233
241
|
|
|
234
242
|
JSON payload passed to Python method.
|
|
@@ -261,6 +269,8 @@ Unknown value falls back to `innerHTML`.
|
|
|
261
269
|
|
|
262
270
|
CSS selector of element receiving `.py-waiting` while request is in flight.
|
|
263
271
|
If missing, empty, invalid, or unresolvable, the triggering element is used.
|
|
272
|
+
The selector is resolved when each accepted request starts, so replacement
|
|
273
|
+
markup can provide the wait target for later requests.
|
|
264
274
|
|
|
265
275
|
### `py-policy` (optional)
|
|
266
276
|
|
|
@@ -279,6 +289,11 @@ Per-element concurrency override. If omitted, the global
|
|
|
279
289
|
- `settleDelay` (ms, default: `20`)
|
|
280
290
|
- `requestPolicy` (`"latest-wins"` default, or `"drop"`)
|
|
281
291
|
|
|
292
|
+
Runtime defaults are read for each accepted request. An element's explicit
|
|
293
|
+
`py-swap` continues to override `defaultSwapStyle`; otherwise, changing
|
|
294
|
+
`defaultSwapStyle` affects its next request even when the element is already
|
|
295
|
+
bound.
|
|
296
|
+
|
|
282
297
|
Example:
|
|
283
298
|
|
|
284
299
|
```js
|
|
@@ -297,21 +312,39 @@ window.pywebviewHtmx.config.settleDelay = 50;
|
|
|
297
312
|
- `py:ignored` (when `requestPolicy="drop"` and request is in flight)
|
|
298
313
|
- `py:error`
|
|
299
314
|
|
|
315
|
+
These lifecycle events are observational and non-cancelable. Calling
|
|
316
|
+
`preventDefault()` on them does not change request or swap behavior. Every
|
|
317
|
+
`py:error` event includes `event.detail.stale`, a boolean indicating whether a
|
|
318
|
+
newer request had already been issued in the same request scope when the error
|
|
319
|
+
was emitted. Errors from stale requests remain observable even though stale
|
|
320
|
+
successful responses do not update the DOM.
|
|
321
|
+
|
|
322
|
+
`event.detail.element` identifies the original control. If that control has
|
|
323
|
+
been removed, the runtime dispatches the error on `document.body` so application
|
|
324
|
+
listeners can still receive it.
|
|
325
|
+
|
|
300
326
|
Example:
|
|
301
327
|
|
|
302
328
|
```js
|
|
303
329
|
document.body.addEventListener("py:error", (event) => {
|
|
304
|
-
console.error("pywebview-htmx error",
|
|
330
|
+
console.error("pywebview-htmx error", {
|
|
331
|
+
error: event.detail.error,
|
|
332
|
+
stale: event.detail.stale,
|
|
333
|
+
});
|
|
305
334
|
});
|
|
306
335
|
```
|
|
307
336
|
|
|
308
337
|
## Concurrency Behavior
|
|
309
338
|
|
|
310
|
-
`pywebview-htmx`
|
|
311
|
-
|
|
339
|
+
For a nonempty `py-target`, `pywebview-htmx` scopes request state by its
|
|
340
|
+
normalized selector string. Controls with the same selector coordinate request
|
|
341
|
+
ordering and `drop` behavior even when a swap replaces the matching DOM node.
|
|
342
|
+
Selector state is discarded after its in-flight requests finish. Controls
|
|
312
343
|
without `py-target` use the triggering element as their request state scope.
|
|
313
|
-
|
|
314
|
-
|
|
344
|
+
|
|
345
|
+
The `py-wait` selector is resolved for each accepted request. Loading state is
|
|
346
|
+
counted separately per resolved wait element, so a shared spinner stays active
|
|
347
|
+
until every request using that particular element finishes.
|
|
315
348
|
|
|
316
349
|
### `latest-wins` (default)
|
|
317
350
|
|
|
@@ -346,10 +379,7 @@ Set `theme=None` to disable automatic theme injection.
|
|
|
346
379
|
|
|
347
380
|
### Built-in Component Classes
|
|
348
381
|
|
|
349
|
-
The bundled CSS
|
|
350
|
-
the demo (`.hero`, `.demo-card`, `.btn`, etc.).
|
|
351
|
-
|
|
352
|
-
Recommended canonical classes:
|
|
382
|
+
The bundled CSS exposes canonical `pyh-*` component classes:
|
|
353
383
|
|
|
354
384
|
- Layout: `.pyh-shell`, `.pyh-hero`, `.pyh-grid`, `.pyh-card`
|
|
355
385
|
- Controls: `.pyh-btn`, `.pyh-btn-primary`, `.pyh-btn-secondary`, `.pyh-btn-ghost`, `.pyh-btn-danger`
|
|
@@ -80,12 +80,14 @@ The skill includes:
|
|
|
80
80
|
## Quick Start
|
|
81
81
|
|
|
82
82
|
```python
|
|
83
|
+
from html import escape
|
|
84
|
+
|
|
83
85
|
from pywebview_htmx import create_window
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
class API:
|
|
87
89
|
def greeting(self, params: dict) -> str:
|
|
88
|
-
name = params.get("name", "world")
|
|
90
|
+
name = escape(str(params.get("name", "world")))
|
|
89
91
|
return f"<p>Hello, {name}!</p>"
|
|
90
92
|
|
|
91
93
|
|
|
@@ -189,6 +191,12 @@ DOM event name to bind. Default is `click`.
|
|
|
189
191
|
<div py-call="show_tip" py-trigger="mouseenter">...</div>
|
|
190
192
|
```
|
|
191
193
|
|
|
194
|
+
When the runtime handles a native action that would leave or destructively
|
|
195
|
+
change the current document, it prevents that default action. This includes
|
|
196
|
+
form submission, link navigation, and submit/reset controls associated with a
|
|
197
|
+
form. Ordinary buttons and the native state changes of checkboxes, radio
|
|
198
|
+
buttons, and labels are preserved.
|
|
199
|
+
|
|
192
200
|
### `data-py-params` (optional)
|
|
193
201
|
|
|
194
202
|
JSON payload passed to Python method.
|
|
@@ -221,6 +229,8 @@ Unknown value falls back to `innerHTML`.
|
|
|
221
229
|
|
|
222
230
|
CSS selector of element receiving `.py-waiting` while request is in flight.
|
|
223
231
|
If missing, empty, invalid, or unresolvable, the triggering element is used.
|
|
232
|
+
The selector is resolved when each accepted request starts, so replacement
|
|
233
|
+
markup can provide the wait target for later requests.
|
|
224
234
|
|
|
225
235
|
### `py-policy` (optional)
|
|
226
236
|
|
|
@@ -239,6 +249,11 @@ Per-element concurrency override. If omitted, the global
|
|
|
239
249
|
- `settleDelay` (ms, default: `20`)
|
|
240
250
|
- `requestPolicy` (`"latest-wins"` default, or `"drop"`)
|
|
241
251
|
|
|
252
|
+
Runtime defaults are read for each accepted request. An element's explicit
|
|
253
|
+
`py-swap` continues to override `defaultSwapStyle`; otherwise, changing
|
|
254
|
+
`defaultSwapStyle` affects its next request even when the element is already
|
|
255
|
+
bound.
|
|
256
|
+
|
|
242
257
|
Example:
|
|
243
258
|
|
|
244
259
|
```js
|
|
@@ -257,21 +272,39 @@ window.pywebviewHtmx.config.settleDelay = 50;
|
|
|
257
272
|
- `py:ignored` (when `requestPolicy="drop"` and request is in flight)
|
|
258
273
|
- `py:error`
|
|
259
274
|
|
|
275
|
+
These lifecycle events are observational and non-cancelable. Calling
|
|
276
|
+
`preventDefault()` on them does not change request or swap behavior. Every
|
|
277
|
+
`py:error` event includes `event.detail.stale`, a boolean indicating whether a
|
|
278
|
+
newer request had already been issued in the same request scope when the error
|
|
279
|
+
was emitted. Errors from stale requests remain observable even though stale
|
|
280
|
+
successful responses do not update the DOM.
|
|
281
|
+
|
|
282
|
+
`event.detail.element` identifies the original control. If that control has
|
|
283
|
+
been removed, the runtime dispatches the error on `document.body` so application
|
|
284
|
+
listeners can still receive it.
|
|
285
|
+
|
|
260
286
|
Example:
|
|
261
287
|
|
|
262
288
|
```js
|
|
263
289
|
document.body.addEventListener("py:error", (event) => {
|
|
264
|
-
console.error("pywebview-htmx error",
|
|
290
|
+
console.error("pywebview-htmx error", {
|
|
291
|
+
error: event.detail.error,
|
|
292
|
+
stale: event.detail.stale,
|
|
293
|
+
});
|
|
265
294
|
});
|
|
266
295
|
```
|
|
267
296
|
|
|
268
297
|
## Concurrency Behavior
|
|
269
298
|
|
|
270
|
-
`pywebview-htmx`
|
|
271
|
-
|
|
299
|
+
For a nonempty `py-target`, `pywebview-htmx` scopes request state by its
|
|
300
|
+
normalized selector string. Controls with the same selector coordinate request
|
|
301
|
+
ordering and `drop` behavior even when a swap replaces the matching DOM node.
|
|
302
|
+
Selector state is discarded after its in-flight requests finish. Controls
|
|
272
303
|
without `py-target` use the triggering element as their request state scope.
|
|
273
|
-
|
|
274
|
-
|
|
304
|
+
|
|
305
|
+
The `py-wait` selector is resolved for each accepted request. Loading state is
|
|
306
|
+
counted separately per resolved wait element, so a shared spinner stays active
|
|
307
|
+
until every request using that particular element finishes.
|
|
275
308
|
|
|
276
309
|
### `latest-wins` (default)
|
|
277
310
|
|
|
@@ -306,10 +339,7 @@ Set `theme=None` to disable automatic theme injection.
|
|
|
306
339
|
|
|
307
340
|
### Built-in Component Classes
|
|
308
341
|
|
|
309
|
-
The bundled CSS
|
|
310
|
-
the demo (`.hero`, `.demo-card`, `.btn`, etc.).
|
|
311
|
-
|
|
312
|
-
Recommended canonical classes:
|
|
342
|
+
The bundled CSS exposes canonical `pyh-*` component classes:
|
|
313
343
|
|
|
314
344
|
- Layout: `.pyh-shell`, `.pyh-hero`, `.pyh-grid`, `.pyh-card`
|
|
315
345
|
- Controls: `.pyh-btn`, `.pyh-btn-primary`, `.pyh-btn-secondary`, `.pyh-btn-ghost`, `.pyh-btn-danger`
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
22
|
"Topic :: Internet :: WWW/HTTP",
|
|
23
23
|
]
|
|
24
|
-
version = "0.
|
|
24
|
+
version = "0.4.1"
|
|
25
25
|
|
|
26
26
|
[project.license]
|
|
27
27
|
file = "LICENSE"
|
|
@@ -32,22 +32,14 @@ requires = [
|
|
|
32
32
|
]
|
|
33
33
|
build-backend = "pdm.backend"
|
|
34
34
|
|
|
35
|
-
[
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
[tool.pdm.build]
|
|
42
|
-
package-dir = "src"
|
|
43
|
-
includes = [
|
|
44
|
-
"src/pywebview_htmx",
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=9.1.1",
|
|
38
|
+
"ruff>=0.15.19",
|
|
39
|
+
"playwright>=1.61.0",
|
|
40
|
+
"pytest-playwright>=0.8.0",
|
|
45
41
|
]
|
|
46
42
|
|
|
47
|
-
[tool.pdm.scripts]
|
|
48
|
-
test = "pytest"
|
|
49
|
-
lint = "ruff check ."
|
|
50
|
-
|
|
51
43
|
[tool.pytest.ini_options]
|
|
52
44
|
testpaths = [
|
|
53
45
|
"tests",
|
|
@@ -63,8 +55,19 @@ select = [
|
|
|
63
55
|
"W",
|
|
64
56
|
]
|
|
65
57
|
|
|
66
|
-
[
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
[tool.pdm]
|
|
59
|
+
distribution = true
|
|
60
|
+
|
|
61
|
+
[tool.pdm.version]
|
|
62
|
+
source = "scm"
|
|
63
|
+
|
|
64
|
+
[tool.pdm.build]
|
|
65
|
+
package-dir = "src"
|
|
66
|
+
includes = [
|
|
67
|
+
"src/pywebview_htmx",
|
|
70
68
|
]
|
|
69
|
+
|
|
70
|
+
[tool.pdm.scripts]
|
|
71
|
+
test = "pytest"
|
|
72
|
+
lint = "ruff check ."
|
|
73
|
+
install-browser = "playwright install chromium"
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
settleDelay: 20,
|
|
8
8
|
requestPolicy: "latest-wins",
|
|
9
9
|
};
|
|
10
|
-
const
|
|
10
|
+
const selfRequestState = new WeakMap();
|
|
11
|
+
const targetedRequestState = new Map();
|
|
11
12
|
const waitState = new WeakMap();
|
|
13
|
+
const boundElements = new WeakSet();
|
|
12
14
|
|
|
13
15
|
function $(selector, root = document) {
|
|
14
16
|
if (!selector) return null;
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
function triggerEvent(target, eventName, detail = {}) {
|
|
24
26
|
const event = new CustomEvent(eventName, {
|
|
25
27
|
bubbles: true,
|
|
26
|
-
cancelable:
|
|
28
|
+
cancelable: false,
|
|
27
29
|
detail,
|
|
28
30
|
});
|
|
29
31
|
target.dispatchEvent(event);
|
|
@@ -132,20 +134,21 @@
|
|
|
132
134
|
return await func(params);
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
+
function getRequestScope(element, targetSelector) {
|
|
138
|
+
const key = targetSelector || element;
|
|
139
|
+
const store = targetSelector ? targetedRequestState : selfRequestState;
|
|
140
|
+
let state = store.get(key);
|
|
137
141
|
if (!state) {
|
|
138
142
|
state = { lastIssued: 0, inFlightCount: 0 };
|
|
139
|
-
|
|
143
|
+
store.set(key, state);
|
|
140
144
|
}
|
|
141
|
-
return state;
|
|
145
|
+
return { key, state, store };
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
function
|
|
145
|
-
if (
|
|
146
|
-
|
|
148
|
+
function releaseRequestScope(scope) {
|
|
149
|
+
if (scope.state.inFlightCount === 0 && scope.store.get(scope.key) === scope.state) {
|
|
150
|
+
scope.store.delete(scope.key);
|
|
147
151
|
}
|
|
148
|
-
return $(targetSelector) || element;
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
function addWaiting(waitTarget) {
|
|
@@ -164,8 +167,24 @@
|
|
|
164
167
|
waitState.set(waitTarget, count);
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
function shouldPreventDefault(eventName) {
|
|
168
|
-
|
|
170
|
+
function shouldPreventDefault(element, eventName) {
|
|
171
|
+
if (eventName === "submit") {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (eventName !== "click") {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if ((element.tagName === "A" || element.tagName === "AREA") && element.hasAttribute("href")) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
if (element.tagName === "BUTTON" && element.form) {
|
|
182
|
+
return element.type !== "button";
|
|
183
|
+
}
|
|
184
|
+
if (element.tagName === "INPUT" && element.form) {
|
|
185
|
+
return ["image", "reset", "submit"].includes(element.type);
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
function getRequestPolicy(element) {
|
|
@@ -188,29 +207,28 @@
|
|
|
188
207
|
elements.push(...root.querySelectorAll("[py-call]"));
|
|
189
208
|
|
|
190
209
|
elements.forEach((element) => {
|
|
191
|
-
if (
|
|
210
|
+
if (boundElements.has(element)) {
|
|
192
211
|
return;
|
|
193
212
|
}
|
|
194
|
-
element.setAttribute("data-pywebview-bound", "true");
|
|
195
213
|
|
|
196
214
|
const functionName = element.getAttribute("py-call");
|
|
197
215
|
const eventName = element.getAttribute("py-trigger") || "click";
|
|
198
|
-
const targetSelector = element.getAttribute("py-target");
|
|
199
|
-
const swapStyle = element.getAttribute("py-swap") || config.defaultSwapStyle;
|
|
200
|
-
const waitTarget = getWaitTarget(element);
|
|
216
|
+
const targetSelector = (element.getAttribute("py-target") || "").trim() || null;
|
|
201
217
|
|
|
202
218
|
if (!functionName) {
|
|
203
219
|
console.warn("PyWebview HTMX: py-call is empty", element);
|
|
204
220
|
return;
|
|
205
221
|
}
|
|
222
|
+
boundElements.add(element);
|
|
206
223
|
|
|
207
224
|
element.addEventListener(eventName, async (event) => {
|
|
208
|
-
if (shouldPreventDefault(eventName) && event.cancelable) {
|
|
225
|
+
if (shouldPreventDefault(element, eventName) && event.cancelable) {
|
|
209
226
|
event.preventDefault();
|
|
210
227
|
}
|
|
211
228
|
|
|
212
229
|
const params = buildRequestParams(element, eventName, event);
|
|
213
|
-
const
|
|
230
|
+
const scope = getRequestScope(element, targetSelector);
|
|
231
|
+
const state = scope.state;
|
|
214
232
|
const requestPolicy = getRequestPolicy(element);
|
|
215
233
|
if (requestPolicy === "drop" && state.inFlightCount > 0) {
|
|
216
234
|
triggerEvent(element, "py:ignored", { reason: "in-flight" });
|
|
@@ -227,6 +245,8 @@
|
|
|
227
245
|
requestId,
|
|
228
246
|
});
|
|
229
247
|
|
|
248
|
+
const swapStyle = element.getAttribute("py-swap") || config.defaultSwapStyle;
|
|
249
|
+
const waitTarget = getWaitTarget(element);
|
|
230
250
|
addWaiting(waitTarget);
|
|
231
251
|
|
|
232
252
|
try {
|
|
@@ -238,7 +258,7 @@
|
|
|
238
258
|
throw new Error("PyWebview HTMX: Python API methods must return an HTML string");
|
|
239
259
|
}
|
|
240
260
|
|
|
241
|
-
|
|
261
|
+
let target = targetSelector ? $(targetSelector) : element;
|
|
242
262
|
if (!target) {
|
|
243
263
|
console.warn("PyWebview HTMX: target element not found", targetSelector);
|
|
244
264
|
return;
|
|
@@ -252,6 +272,12 @@
|
|
|
252
272
|
return;
|
|
253
273
|
}
|
|
254
274
|
|
|
275
|
+
target = targetSelector ? $(targetSelector) : element;
|
|
276
|
+
if (!target || !target.isConnected) {
|
|
277
|
+
console.warn("PyWebview HTMX: target element not found", targetSelector);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
255
281
|
const processRoot = pySwap(target, response, swapStyle);
|
|
256
282
|
const afterSwapTarget =
|
|
257
283
|
swapStyle === "outerHTML"
|
|
@@ -265,9 +291,16 @@
|
|
|
265
291
|
}
|
|
266
292
|
} catch (error) {
|
|
267
293
|
console.error("PyWebview HTMX: error calling Python function", error);
|
|
268
|
-
|
|
294
|
+
const errorTarget = element.isConnected ? element : document.body || document;
|
|
295
|
+
triggerEvent(errorTarget, "py:error", {
|
|
296
|
+
element,
|
|
297
|
+
error,
|
|
298
|
+
requestId,
|
|
299
|
+
stale: requestId !== state.lastIssued,
|
|
300
|
+
});
|
|
269
301
|
} finally {
|
|
270
302
|
state.inFlightCount = Math.max(0, state.inFlightCount - 1);
|
|
303
|
+
releaseRequestScope(scope);
|
|
271
304
|
removeWaiting(waitTarget);
|
|
272
305
|
}
|
|
273
306
|
});
|