pywebview-htmx 0.3.0__tar.gz → 0.4.0__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.0 → pywebview_htmx-0.4.0}/PKG-INFO +49 -14
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/README.md +47 -12
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/pyproject.toml +24 -21
- pywebview_htmx-0.4.0/src/pywebview_htmx/py.typed +1 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/static/runtime.js +70 -19
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/static/themes/base.css +44 -102
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/static/themes/cybermind.css +4 -13
- pywebview_htmx-0.4.0/tests/test_js_runtime.py +642 -0
- pywebview_htmx-0.4.0/tests/test_repo_legibility.py +125 -0
- pywebview_htmx-0.3.0/tests/test_js_contract_config_and_binding.py +0 -76
- pywebview_htmx-0.3.0/tests/test_js_contract_ergonomics.py +0 -125
- pywebview_htmx-0.3.0/tests/test_js_contract_request_and_swap.py +0 -83
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/LICENSE +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/__init__.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/runtime.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/static/themes/aurora.css +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/src/pywebview_htmx/static/themes/paper.css +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/tests/conftest.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/tests/test_python_create_window.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/tests/test_python_injection.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/tests/test_python_params_attr.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/tests/test_python_script_loading.py +0 -0
- {pywebview_htmx-0.3.0 → pywebview_htmx-0.4.0}/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.0
|
|
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
|
|
@@ -35,7 +35,7 @@ Classifier: Intended Audience :: Developers
|
|
|
35
35
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
36
36
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
37
37
|
Requires-Python: >=3.14
|
|
38
|
-
Requires-Dist: pywebview>=6.1
|
|
38
|
+
Requires-Dist: pywebview>=6.2.1
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
|
|
41
41
|
# pywebview-htmx
|
|
@@ -84,6 +84,11 @@ pdm install
|
|
|
84
84
|
pdm add pywebview-htmx
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
## Maintainer Docs
|
|
88
|
+
|
|
89
|
+
For maintainer and agent-facing repository guidance, see
|
|
90
|
+
[`docs/index.md`](docs/index.md).
|
|
91
|
+
|
|
87
92
|
## Agent Skill
|
|
88
93
|
|
|
89
94
|
This repository includes an installable coding-agent skill at
|
|
@@ -115,12 +120,14 @@ The skill includes:
|
|
|
115
120
|
## Quick Start
|
|
116
121
|
|
|
117
122
|
```python
|
|
123
|
+
from html import escape
|
|
124
|
+
|
|
118
125
|
from pywebview_htmx import create_window
|
|
119
126
|
|
|
120
127
|
|
|
121
128
|
class API:
|
|
122
129
|
def greeting(self, params: dict) -> str:
|
|
123
|
-
name = params.get("name", "world")
|
|
130
|
+
name = escape(str(params.get("name", "world")))
|
|
124
131
|
return f"<p>Hello, {name}!</p>"
|
|
125
132
|
|
|
126
133
|
|
|
@@ -224,6 +231,12 @@ DOM event name to bind. Default is `click`.
|
|
|
224
231
|
<div py-call="show_tip" py-trigger="mouseenter">...</div>
|
|
225
232
|
```
|
|
226
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
|
+
|
|
227
240
|
### `data-py-params` (optional)
|
|
228
241
|
|
|
229
242
|
JSON payload passed to Python method.
|
|
@@ -255,7 +268,9 @@ Unknown value falls back to `innerHTML`.
|
|
|
255
268
|
### `py-wait` (optional)
|
|
256
269
|
|
|
257
270
|
CSS selector of element receiving `.py-waiting` while request is in flight.
|
|
258
|
-
If missing
|
|
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.
|
|
259
274
|
|
|
260
275
|
### `py-policy` (optional)
|
|
261
276
|
|
|
@@ -274,6 +289,11 @@ Per-element concurrency override. If omitted, the global
|
|
|
274
289
|
- `settleDelay` (ms, default: `20`)
|
|
275
290
|
- `requestPolicy` (`"latest-wins"` default, or `"drop"`)
|
|
276
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
|
+
|
|
277
297
|
Example:
|
|
278
298
|
|
|
279
299
|
```js
|
|
@@ -292,27 +312,45 @@ window.pywebviewHtmx.config.settleDelay = 50;
|
|
|
292
312
|
- `py:ignored` (when `requestPolicy="drop"` and request is in flight)
|
|
293
313
|
- `py:error`
|
|
294
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
|
+
|
|
295
322
|
Example:
|
|
296
323
|
|
|
297
324
|
```js
|
|
298
325
|
document.body.addEventListener("py:error", (event) => {
|
|
299
|
-
console.error("pywebview-htmx error",
|
|
326
|
+
console.error("pywebview-htmx error", {
|
|
327
|
+
error: event.detail.error,
|
|
328
|
+
stale: event.detail.stale,
|
|
329
|
+
});
|
|
300
330
|
});
|
|
301
331
|
```
|
|
302
332
|
|
|
303
333
|
## Concurrency Behavior
|
|
304
334
|
|
|
305
|
-
|
|
335
|
+
For a nonempty `py-target`, `pywebview-htmx` scopes request state by its
|
|
336
|
+
normalized selector string. Controls with the same selector coordinate request
|
|
337
|
+
ordering and `drop` behavior even when a swap replaces the matching DOM node.
|
|
338
|
+
Selector state is discarded after its in-flight requests finish. Controls
|
|
339
|
+
without `py-target` use the triggering element as their request state scope.
|
|
340
|
+
|
|
341
|
+
The `py-wait` selector is resolved for each accepted request. Loading state is
|
|
342
|
+
counted separately per resolved wait element, so a shared spinner stays active
|
|
343
|
+
until every request using that particular element finishes.
|
|
306
344
|
|
|
307
345
|
### `latest-wins` (default)
|
|
308
346
|
|
|
309
|
-
Multiple rapid requests are allowed; stale responses
|
|
310
|
-
Only latest request updates the DOM.
|
|
347
|
+
Multiple rapid requests are allowed; stale responses in the same request state
|
|
348
|
+
scope are ignored. Only the latest request for that scope updates the DOM.
|
|
311
349
|
|
|
312
350
|
### `drop`
|
|
313
351
|
|
|
314
|
-
If a request is already in flight for
|
|
315
|
-
`py:ignored` is emitted.
|
|
352
|
+
If a request is already in flight for the same request state scope, new triggers
|
|
353
|
+
are ignored and `py:ignored` is emitted.
|
|
316
354
|
|
|
317
355
|
## Dynamic Content Re-processing
|
|
318
356
|
|
|
@@ -337,10 +375,7 @@ Set `theme=None` to disable automatic theme injection.
|
|
|
337
375
|
|
|
338
376
|
### Built-in Component Classes
|
|
339
377
|
|
|
340
|
-
The bundled CSS
|
|
341
|
-
the demo (`.hero`, `.demo-card`, `.btn`, etc.).
|
|
342
|
-
|
|
343
|
-
Recommended canonical classes:
|
|
378
|
+
The bundled CSS exposes canonical `pyh-*` component classes:
|
|
344
379
|
|
|
345
380
|
- Layout: `.pyh-shell`, `.pyh-hero`, `.pyh-grid`, `.pyh-card`
|
|
346
381
|
- Controls: `.pyh-btn`, `.pyh-btn-primary`, `.pyh-btn-secondary`, `.pyh-btn-ghost`, `.pyh-btn-danger`
|
|
@@ -44,6 +44,11 @@ pdm install
|
|
|
44
44
|
pdm add pywebview-htmx
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
## Maintainer Docs
|
|
48
|
+
|
|
49
|
+
For maintainer and agent-facing repository guidance, see
|
|
50
|
+
[`docs/index.md`](docs/index.md).
|
|
51
|
+
|
|
47
52
|
## Agent Skill
|
|
48
53
|
|
|
49
54
|
This repository includes an installable coding-agent skill at
|
|
@@ -75,12 +80,14 @@ The skill includes:
|
|
|
75
80
|
## Quick Start
|
|
76
81
|
|
|
77
82
|
```python
|
|
83
|
+
from html import escape
|
|
84
|
+
|
|
78
85
|
from pywebview_htmx import create_window
|
|
79
86
|
|
|
80
87
|
|
|
81
88
|
class API:
|
|
82
89
|
def greeting(self, params: dict) -> str:
|
|
83
|
-
name = params.get("name", "world")
|
|
90
|
+
name = escape(str(params.get("name", "world")))
|
|
84
91
|
return f"<p>Hello, {name}!</p>"
|
|
85
92
|
|
|
86
93
|
|
|
@@ -184,6 +191,12 @@ DOM event name to bind. Default is `click`.
|
|
|
184
191
|
<div py-call="show_tip" py-trigger="mouseenter">...</div>
|
|
185
192
|
```
|
|
186
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
|
+
|
|
187
200
|
### `data-py-params` (optional)
|
|
188
201
|
|
|
189
202
|
JSON payload passed to Python method.
|
|
@@ -215,7 +228,9 @@ Unknown value falls back to `innerHTML`.
|
|
|
215
228
|
### `py-wait` (optional)
|
|
216
229
|
|
|
217
230
|
CSS selector of element receiving `.py-waiting` while request is in flight.
|
|
218
|
-
If missing
|
|
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.
|
|
219
234
|
|
|
220
235
|
### `py-policy` (optional)
|
|
221
236
|
|
|
@@ -234,6 +249,11 @@ Per-element concurrency override. If omitted, the global
|
|
|
234
249
|
- `settleDelay` (ms, default: `20`)
|
|
235
250
|
- `requestPolicy` (`"latest-wins"` default, or `"drop"`)
|
|
236
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
|
+
|
|
237
257
|
Example:
|
|
238
258
|
|
|
239
259
|
```js
|
|
@@ -252,27 +272,45 @@ window.pywebviewHtmx.config.settleDelay = 50;
|
|
|
252
272
|
- `py:ignored` (when `requestPolicy="drop"` and request is in flight)
|
|
253
273
|
- `py:error`
|
|
254
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
|
+
|
|
255
282
|
Example:
|
|
256
283
|
|
|
257
284
|
```js
|
|
258
285
|
document.body.addEventListener("py:error", (event) => {
|
|
259
|
-
console.error("pywebview-htmx error",
|
|
286
|
+
console.error("pywebview-htmx error", {
|
|
287
|
+
error: event.detail.error,
|
|
288
|
+
stale: event.detail.stale,
|
|
289
|
+
});
|
|
260
290
|
});
|
|
261
291
|
```
|
|
262
292
|
|
|
263
293
|
## Concurrency Behavior
|
|
264
294
|
|
|
265
|
-
|
|
295
|
+
For a nonempty `py-target`, `pywebview-htmx` scopes request state by its
|
|
296
|
+
normalized selector string. Controls with the same selector coordinate request
|
|
297
|
+
ordering and `drop` behavior even when a swap replaces the matching DOM node.
|
|
298
|
+
Selector state is discarded after its in-flight requests finish. Controls
|
|
299
|
+
without `py-target` use the triggering element as their request state scope.
|
|
300
|
+
|
|
301
|
+
The `py-wait` selector is resolved for each accepted request. Loading state is
|
|
302
|
+
counted separately per resolved wait element, so a shared spinner stays active
|
|
303
|
+
until every request using that particular element finishes.
|
|
266
304
|
|
|
267
305
|
### `latest-wins` (default)
|
|
268
306
|
|
|
269
|
-
Multiple rapid requests are allowed; stale responses
|
|
270
|
-
Only latest request updates the DOM.
|
|
307
|
+
Multiple rapid requests are allowed; stale responses in the same request state
|
|
308
|
+
scope are ignored. Only the latest request for that scope updates the DOM.
|
|
271
309
|
|
|
272
310
|
### `drop`
|
|
273
311
|
|
|
274
|
-
If a request is already in flight for
|
|
275
|
-
`py:ignored` is emitted.
|
|
312
|
+
If a request is already in flight for the same request state scope, new triggers
|
|
313
|
+
are ignored and `py:ignored` is emitted.
|
|
276
314
|
|
|
277
315
|
## Dynamic Content Re-processing
|
|
278
316
|
|
|
@@ -297,10 +335,7 @@ Set `theme=None` to disable automatic theme injection.
|
|
|
297
335
|
|
|
298
336
|
### Built-in Component Classes
|
|
299
337
|
|
|
300
|
-
The bundled CSS
|
|
301
|
-
the demo (`.hero`, `.demo-card`, `.btn`, etc.).
|
|
302
|
-
|
|
303
|
-
Recommended canonical classes:
|
|
338
|
+
The bundled CSS exposes canonical `pyh-*` component classes:
|
|
304
339
|
|
|
305
340
|
- Layout: `.pyh-shell`, `.pyh-hero`, `.pyh-grid`, `.pyh-card`
|
|
306
341
|
- Controls: `.pyh-btn`, `.pyh-btn-primary`, `.pyh-btn-secondary`, `.pyh-btn-ghost`, `.pyh-btn-danger`
|
|
@@ -6,7 +6,7 @@ authors = [
|
|
|
6
6
|
{ name = "B.T. Franklin", email = "brandon.franklin@gmail.com" },
|
|
7
7
|
]
|
|
8
8
|
dependencies = [
|
|
9
|
-
"pywebview>=6.1",
|
|
9
|
+
"pywebview>=6.2.1",
|
|
10
10
|
]
|
|
11
11
|
requires-python = ">=3.14"
|
|
12
12
|
readme = "README.md"
|
|
@@ -21,33 +21,25 @@ classifiers = [
|
|
|
21
21
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
22
|
"Topic :: Internet :: WWW/HTTP",
|
|
23
23
|
]
|
|
24
|
-
version = "0.
|
|
24
|
+
version = "0.4.0"
|
|
25
25
|
|
|
26
26
|
[project.license]
|
|
27
27
|
file = "LICENSE"
|
|
28
28
|
|
|
29
29
|
[build-system]
|
|
30
30
|
requires = [
|
|
31
|
-
"pdm-backend",
|
|
31
|
+
"pdm-backend>=2.4.9",
|
|
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"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -7,17 +7,24 @@
|
|
|
7
7
|
settleDelay: 20,
|
|
8
8
|
requestPolicy: "latest-wins",
|
|
9
9
|
};
|
|
10
|
-
const
|
|
10
|
+
const selfRequestState = new WeakMap();
|
|
11
|
+
const targetedRequestState = new Map();
|
|
12
|
+
const waitState = new WeakMap();
|
|
11
13
|
|
|
12
14
|
function $(selector, root = document) {
|
|
13
15
|
if (!selector) return null;
|
|
14
|
-
|
|
16
|
+
try {
|
|
17
|
+
return root.querySelector(selector);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.warn("PyWebview HTMX: invalid selector", selector, error);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
function triggerEvent(target, eventName, detail = {}) {
|
|
18
25
|
const event = new CustomEvent(eventName, {
|
|
19
26
|
bubbles: true,
|
|
20
|
-
cancelable:
|
|
27
|
+
cancelable: false,
|
|
21
28
|
detail,
|
|
22
29
|
});
|
|
23
30
|
target.dispatchEvent(event);
|
|
@@ -126,17 +133,57 @@
|
|
|
126
133
|
return await func(params);
|
|
127
134
|
}
|
|
128
135
|
|
|
129
|
-
function
|
|
130
|
-
|
|
136
|
+
function getRequestScope(element, targetSelector) {
|
|
137
|
+
const key = targetSelector || element;
|
|
138
|
+
const store = targetSelector ? targetedRequestState : selfRequestState;
|
|
139
|
+
let state = store.get(key);
|
|
131
140
|
if (!state) {
|
|
132
141
|
state = { lastIssued: 0, inFlightCount: 0 };
|
|
133
|
-
|
|
142
|
+
store.set(key, state);
|
|
143
|
+
}
|
|
144
|
+
return { key, state, store };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function releaseRequestScope(scope) {
|
|
148
|
+
if (scope.state.inFlightCount === 0 && scope.store.get(scope.key) === scope.state) {
|
|
149
|
+
scope.store.delete(scope.key);
|
|
134
150
|
}
|
|
135
|
-
return state;
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
function
|
|
139
|
-
|
|
153
|
+
function addWaiting(waitTarget) {
|
|
154
|
+
const count = (waitState.get(waitTarget) || 0) + 1;
|
|
155
|
+
waitState.set(waitTarget, count);
|
|
156
|
+
waitTarget.classList.add("py-waiting");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function removeWaiting(waitTarget) {
|
|
160
|
+
const count = Math.max(0, (waitState.get(waitTarget) || 0) - 1);
|
|
161
|
+
if (count === 0) {
|
|
162
|
+
waitState.delete(waitTarget);
|
|
163
|
+
waitTarget.classList.remove("py-waiting");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
waitState.set(waitTarget, count);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function shouldPreventDefault(element, eventName) {
|
|
170
|
+
if (eventName === "submit") {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (eventName !== "click") {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if ((element.tagName === "A" || element.tagName === "AREA") && element.hasAttribute("href")) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (element.tagName === "BUTTON" && element.form) {
|
|
181
|
+
return element.type !== "button";
|
|
182
|
+
}
|
|
183
|
+
if (element.tagName === "INPUT" && element.form) {
|
|
184
|
+
return ["image", "reset", "submit"].includes(element.type);
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
140
187
|
}
|
|
141
188
|
|
|
142
189
|
function getRequestPolicy(element) {
|
|
@@ -166,9 +213,7 @@
|
|
|
166
213
|
|
|
167
214
|
const functionName = element.getAttribute("py-call");
|
|
168
215
|
const eventName = element.getAttribute("py-trigger") || "click";
|
|
169
|
-
const targetSelector = element.getAttribute("py-target");
|
|
170
|
-
const swapStyle = element.getAttribute("py-swap") || config.defaultSwapStyle;
|
|
171
|
-
const waitTarget = getWaitTarget(element);
|
|
216
|
+
const targetSelector = (element.getAttribute("py-target") || "").trim() || null;
|
|
172
217
|
|
|
173
218
|
if (!functionName) {
|
|
174
219
|
console.warn("PyWebview HTMX: py-call is empty", element);
|
|
@@ -176,12 +221,13 @@
|
|
|
176
221
|
}
|
|
177
222
|
|
|
178
223
|
element.addEventListener(eventName, async (event) => {
|
|
179
|
-
if (shouldPreventDefault(eventName) && event.cancelable) {
|
|
224
|
+
if (shouldPreventDefault(element, eventName) && event.cancelable) {
|
|
180
225
|
event.preventDefault();
|
|
181
226
|
}
|
|
182
227
|
|
|
183
228
|
const params = buildRequestParams(element, eventName, event);
|
|
184
|
-
const
|
|
229
|
+
const scope = getRequestScope(element, targetSelector);
|
|
230
|
+
const state = scope.state;
|
|
185
231
|
const requestPolicy = getRequestPolicy(element);
|
|
186
232
|
if (requestPolicy === "drop" && state.inFlightCount > 0) {
|
|
187
233
|
triggerEvent(element, "py:ignored", { reason: "in-flight" });
|
|
@@ -198,7 +244,9 @@
|
|
|
198
244
|
requestId,
|
|
199
245
|
});
|
|
200
246
|
|
|
201
|
-
|
|
247
|
+
const swapStyle = element.getAttribute("py-swap") || config.defaultSwapStyle;
|
|
248
|
+
const waitTarget = getWaitTarget(element);
|
|
249
|
+
addWaiting(waitTarget);
|
|
202
250
|
|
|
203
251
|
try {
|
|
204
252
|
const response = await invokePython(functionName, params);
|
|
@@ -236,12 +284,15 @@
|
|
|
236
284
|
}
|
|
237
285
|
} catch (error) {
|
|
238
286
|
console.error("PyWebview HTMX: error calling Python function", error);
|
|
239
|
-
triggerEvent(element, "py:error", {
|
|
287
|
+
triggerEvent(element, "py:error", {
|
|
288
|
+
error,
|
|
289
|
+
requestId,
|
|
290
|
+
stale: requestId !== state.lastIssued,
|
|
291
|
+
});
|
|
240
292
|
} finally {
|
|
241
293
|
state.inFlightCount = Math.max(0, state.inFlightCount - 1);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
294
|
+
releaseRequestScope(scope);
|
|
295
|
+
removeWaiting(waitTarget);
|
|
245
296
|
}
|
|
246
297
|
});
|
|
247
298
|
});
|