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.
Files changed (18) hide show
  1. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/PKG-INFO +1 -1
  2. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/pyproject.toml +5 -1
  3. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/runtime.py +4 -1
  4. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/runtime.js +10 -2
  5. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/base.css +7 -2
  6. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_js_contract_request_and_swap.py +17 -2
  7. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_injection.py +7 -0
  8. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/LICENSE +0 -0
  9. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/README.md +0 -0
  10. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/__init__.py +0 -0
  11. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/aurora.css +0 -0
  12. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/cybermind.css +0 -0
  13. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/src/pywebview_htmx/static/themes/paper.css +0 -0
  14. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/conftest.py +0 -0
  15. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_js_contract_config_and_binding.py +0 -0
  16. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_create_window.py +0 -0
  17. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.1}/tests/test_python_script_loading.py +0 -0
  18. {pywebview_htmx-0.1.0 → pywebview_htmx-0.2.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.1.0
3
+ Version: 0.2.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pywebview-htmx"
3
- version = "0.1.0"
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(r"<script[^>]*\bdata-pywebview-htmx\b", re.IGNORECASE)
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
- triggerEvent(target, "py:afterSwap", { response, requestId });
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.55;
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
- (73, ("if (event.cancelable)", "event.preventDefault();")),
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
- (89, ('triggerEvent(target, "py:afterSwap"',)),
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