pywebview-htmx 0.1.0__tar.gz → 0.2.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.1.0 → pywebview_htmx-0.2.1}/PKG-INFO +1 -1
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/pyproject.toml +5 -1
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/runtime.py +4 -1
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/runtime.js +10 -2
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/base.css +7 -2
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_js_contract_request_and_swap.py +17 -2
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_injection.py +7 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/LICENSE +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/README.md +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/__init__.py +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/aurora.css +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/cybermind.css +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/paper.css +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/conftest.py +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_js_contract_config_and_binding.py +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_create_window.py +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_script_loading.py +0 -0
- {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_themes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pywebview-htmx"
|
|
3
|
-
|
|
3
|
+
dynamic = []
|
|
4
4
|
description = "HTMX-style declarative UI bindings for PyWebview apps"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "B.T. Franklin", email = "brandon.franklin@gmail.com" },
|
|
@@ -21,6 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
22
|
"Topic :: Internet :: WWW/HTTP",
|
|
23
23
|
]
|
|
24
|
+
version = "0.2.1"
|
|
24
25
|
|
|
25
26
|
[project.license]
|
|
26
27
|
file = "LICENSE"
|
|
@@ -34,6 +35,9 @@ build-backend = "pdm.backend"
|
|
|
34
35
|
[tool.pdm]
|
|
35
36
|
distribution = true
|
|
36
37
|
|
|
38
|
+
[tool.pdm.version]
|
|
39
|
+
source = "scm"
|
|
40
|
+
|
|
37
41
|
[tool.pdm.build]
|
|
38
42
|
package-dir = "src"
|
|
39
43
|
includes = [
|
|
@@ -6,7 +6,10 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
import webview
|
|
8
8
|
|
|
9
|
-
_SCRIPT_TAG_RE = re.compile(
|
|
9
|
+
_SCRIPT_TAG_RE = re.compile(
|
|
10
|
+
r"<script[^>]*\bdata-pywebview-htmx\s*=\s*(?:[\"']true[\"']|true\b)",
|
|
11
|
+
re.IGNORECASE,
|
|
12
|
+
)
|
|
10
13
|
_THEME_TAG_RE = re.compile(
|
|
11
14
|
r"<style[^>]*\bdata-pywebview-theme\s*=\s*[\"'](?P<name>[^\"']+)[\"'][^>]*>.*?</style>",
|
|
12
15
|
re.IGNORECASE | re.DOTALL,
|
|
@@ -94,6 +94,10 @@
|
|
|
94
94
|
return state;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
function shouldPreventDefault(eventName) {
|
|
98
|
+
return eventName === "submit";
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
function processPyWebviewHtmxNodes(root = document) {
|
|
98
102
|
if (!root || typeof root.querySelectorAll !== "function") {
|
|
99
103
|
return;
|
|
@@ -119,7 +123,7 @@
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
element.addEventListener(eventName, async (event) => {
|
|
122
|
-
if (event.cancelable) {
|
|
126
|
+
if (shouldPreventDefault(eventName) && event.cancelable) {
|
|
123
127
|
event.preventDefault();
|
|
124
128
|
}
|
|
125
129
|
|
|
@@ -163,7 +167,11 @@
|
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
const processRoot = pySwap(target, response, swapStyle);
|
|
166
|
-
|
|
170
|
+
const afterSwapTarget =
|
|
171
|
+
swapStyle === "outerHTML"
|
|
172
|
+
? (targetSelector ? $(targetSelector) || processRoot : processRoot)
|
|
173
|
+
: target;
|
|
174
|
+
triggerEvent(afterSwapTarget, "py:afterSwap", { response, requestId });
|
|
167
175
|
processPyWebviewHtmxNodes(processRoot);
|
|
168
176
|
|
|
169
177
|
if (config.settleDelay > 0) {
|
|
@@ -156,6 +156,12 @@ body {
|
|
|
156
156
|
filter: brightness(1.06);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
.pyh-btn:active,
|
|
160
|
+
.btn:active {
|
|
161
|
+
transform: translateY(1px);
|
|
162
|
+
filter: brightness(0.95);
|
|
163
|
+
}
|
|
164
|
+
|
|
159
165
|
.pyh-btn-primary,
|
|
160
166
|
.btn-primary {
|
|
161
167
|
background: var(--pyh-btn-primary-bg);
|
|
@@ -299,8 +305,7 @@ body {
|
|
|
299
305
|
}
|
|
300
306
|
|
|
301
307
|
.py-waiting {
|
|
302
|
-
opacity: 0.
|
|
303
|
-
filter: saturate(0.7);
|
|
308
|
+
opacity: 0.88;
|
|
304
309
|
pointer-events: none;
|
|
305
310
|
}
|
|
306
311
|
|
|
@@ -11,7 +11,15 @@ def _assert_fragments_present(js_compact: str, fragments: tuple[str, ...]) -> No
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
JS_BEHAVIOR_FRAGMENTS: list[tuple[int, tuple[str, ...]]] = [
|
|
14
|
-
(
|
|
14
|
+
(
|
|
15
|
+
73,
|
|
16
|
+
(
|
|
17
|
+
'function shouldPreventDefault(eventName)',
|
|
18
|
+
'return eventName === "submit";',
|
|
19
|
+
"if (shouldPreventDefault(eventName) && event.cancelable)",
|
|
20
|
+
"event.preventDefault();",
|
|
21
|
+
),
|
|
22
|
+
),
|
|
15
23
|
(74, ("let state = requestState.get(element);", "if (!state) {")),
|
|
16
24
|
(75, ("lastIssued: 0", "state.lastIssued = requestId;")),
|
|
17
25
|
(76, ("inFlightCount: 0", "state.inFlightCount += 1;")),
|
|
@@ -27,7 +35,14 @@ JS_BEHAVIOR_FRAGMENTS: list[tuple[int, tuple[str, ...]]] = [
|
|
|
27
35
|
(86, ("if (requestId !== state.lastIssued) { return; }",)),
|
|
28
36
|
(87, ("const processRoot = pySwap(target, response, swapStyle);",)),
|
|
29
37
|
(88, ("processPyWebviewHtmxNodes(processRoot);",)),
|
|
30
|
-
(
|
|
38
|
+
(
|
|
39
|
+
89,
|
|
40
|
+
(
|
|
41
|
+
"const afterSwapTarget =",
|
|
42
|
+
'swapStyle === "outerHTML"',
|
|
43
|
+
'triggerEvent(afterSwapTarget, "py:afterSwap"',
|
|
44
|
+
),
|
|
45
|
+
),
|
|
31
46
|
(90, ("if (config.settleDelay > 0)", "await delay(config.settleDelay);")),
|
|
32
47
|
(91, ('triggerEvent(element, "py:error"', "requestId")),
|
|
33
48
|
(92, ("state.inFlightCount = Math.max(0, state.inFlightCount - 1);",)),
|
|
@@ -90,6 +90,13 @@ def test_non_script_data_marker_does_not_block_injection() -> None:
|
|
|
90
90
|
assert result.count(SCRIPT_MARKER) == 1
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
def test_script_with_false_marker_does_not_block_injection() -> None:
|
|
94
|
+
html = '<html><body><script data-pywebview-htmx="false"></script></body></html>'
|
|
95
|
+
result = inject_runtime(html)
|
|
96
|
+
assert result != html
|
|
97
|
+
assert result.count(SCRIPT_MARKER) == 1
|
|
98
|
+
|
|
99
|
+
|
|
93
100
|
def test_empty_html_input_gets_injected_script() -> None:
|
|
94
101
|
result = inject_runtime("")
|
|
95
102
|
assert MARKER in result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/cybermind.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|