spaday 0.2.0__tar.gz → 0.2.2__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 (164) hide show
  1. {spaday-0.2.0 → spaday-0.2.2}/Cargo.lock +9 -9
  2. {spaday-0.2.0 → spaday-0.2.2}/Cargo.toml +1 -1
  3. {spaday-0.2.0 → spaday-0.2.2}/PKG-INFO +1 -2
  4. {spaday-0.2.0 → spaday-0.2.2}/js/Cargo.toml +1 -1
  5. {spaday-0.2.0 → spaday-0.2.2}/js/build.mjs +8 -0
  6. {spaday-0.2.0 → spaday-0.2.2}/js/package.json +1 -1
  7. {spaday-0.2.0 → spaday-0.2.2}/js/src/rust/lib.rs +14 -3
  8. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/actions.ts +2 -2
  9. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/signals.ts +4 -0
  10. {spaday-0.2.0 → spaday-0.2.2}/js/tests/actions.spec.js +37 -0
  11. {spaday-0.2.0 → spaday-0.2.2}/js/tests/signals.spec.js +32 -0
  12. {spaday-0.2.0 → spaday-0.2.2}/pyproject.toml +3 -4
  13. {spaday-0.2.0 → spaday-0.2.2}/rust/Cargo.toml +1 -1
  14. spaday-0.2.2/rust/coverage +1347 -0
  15. {spaday-0.2.0 → spaday-0.2.2}/rust/src/action.rs +54 -3
  16. {spaday-0.2.0 → spaday-0.2.2}/rust/src/lib.rs +1 -1
  17. {spaday-0.2.0 → spaday-0.2.2}/spaday/__init__.py +3 -1
  18. {spaday-0.2.0 → spaday-0.2.2}/spaday/actions.py +25 -6
  19. {spaday-0.2.0 → spaday-0.2.2}/spaday/backends/aiohttp.py +16 -3
  20. {spaday-0.2.0 → spaday-0.2.2}/spaday/backends/flask.py +16 -3
  21. {spaday-0.2.0 → spaday-0.2.2}/spaday/backends/starlette.py +5 -2
  22. {spaday-0.2.0 → spaday-0.2.2}/spaday/backends/tornado.py +16 -3
  23. {spaday-0.2.0 → spaday-0.2.2}/spaday/bootstrap.py +59 -22
  24. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/shell.py +24 -7
  25. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/index.js +23 -13
  26. spaday-0.2.2/spaday/extension/cdn/index.js.map +7 -0
  27. spaday-0.2.2/spaday/extension/cdn/widget.js +1017 -0
  28. spaday-0.2.2/spaday/extension/cdn/widget.js.map +7 -0
  29. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/widget.webawesome.js +24 -14
  30. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/widget.webawesome.js.map +2 -2
  31. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/pkg/spaday.js +20 -12
  32. spaday-0.2.2/spaday/extension/pkg/spaday_bg.wasm +0 -0
  33. spaday-0.2.2/spaday/extension/transports/cdn/index.js +608 -0
  34. spaday-0.2.2/spaday/extension/transports/cdn/index.js.map +7 -0
  35. spaday-0.2.2/spaday/extension/transports/pkg/transports.d.ts +115 -0
  36. spaday-0.2.2/spaday/extension/transports/pkg/transports.js +501 -0
  37. spaday-0.2.2/spaday/extension/transports/pkg/transports_bg.wasm +0 -0
  38. spaday-0.2.2/spaday/extension/transports/pkg/transports_bg.wasm.d.ts +23 -0
  39. spaday-0.2.2/spaday/spaday.abi3.so +0 -0
  40. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_actions.py +14 -0
  41. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_backend_starlette.py +10 -0
  42. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_bootstrap.py +22 -4
  43. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_shell.py +41 -0
  44. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_validate.py +8 -0
  45. {spaday-0.2.0 → spaday-0.2.2}/spaday/validate.py +9 -3
  46. spaday-0.2.0/rust/coverage +0 -1311
  47. spaday-0.2.0/spaday/extension/cdn/index.js.map +0 -7
  48. spaday-0.2.0/spaday/extension/cdn/widget.js +0 -1007
  49. spaday-0.2.0/spaday/extension/cdn/widget.js.map +0 -7
  50. spaday-0.2.0/spaday/extension/pkg/spaday_bg.wasm +0 -0
  51. spaday-0.2.0/spaday/spaday.abi3.so +0 -0
  52. {spaday-0.2.0 → spaday-0.2.2}/.gitignore +0 -0
  53. {spaday-0.2.0 → spaday-0.2.2}/LICENSE +0 -0
  54. {spaday-0.2.0 → spaday-0.2.2}/README.md +0 -0
  55. {spaday-0.2.0 → spaday-0.2.2}/js/playwright.config.js +0 -0
  56. {spaday-0.2.0 → spaday-0.2.2}/js/pnpm-lock.yaml +0 -0
  57. {spaday-0.2.0 → spaday-0.2.2}/js/pnpm-workspace.yaml +0 -0
  58. {spaday-0.2.0 → spaday-0.2.2}/js/src/css/index.css +0 -0
  59. {spaday-0.2.0 → spaday-0.2.2}/js/src/html/index.html +0 -0
  60. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/cem.ts +0 -0
  61. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/css.d.ts +0 -0
  62. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/examples/gateway.ts +0 -0
  63. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/handlers.ts +0 -0
  64. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/index.ts +0 -0
  65. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/runtime.ts +0 -0
  66. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/shell.ts +0 -0
  67. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/store-sync.ts +0 -0
  68. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/value.ts +0 -0
  69. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/wasm-ready.ts +0 -0
  70. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/wasm.d.ts +0 -0
  71. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/webawesome.ts +0 -0
  72. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/widget.ts +0 -0
  73. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/wrappers/lightweight-chart.ts +0 -0
  74. {spaday-0.2.0 → spaday-0.2.2}/js/src/ts/wrappers/perspective-workspace.ts +0 -0
  75. {spaday-0.2.0 → spaday-0.2.2}/js/tests/cem.spec.js +0 -0
  76. {spaday-0.2.0 → spaday-0.2.2}/js/tests/hydrate.spec.js +0 -0
  77. {spaday-0.2.0 → spaday-0.2.2}/js/tests/index.spec.js +0 -0
  78. {spaday-0.2.0 → spaday-0.2.2}/js/tests/layout.html +0 -0
  79. {spaday-0.2.0 → spaday-0.2.2}/js/tests/layout.spec.js +0 -0
  80. {spaday-0.2.0 → spaday-0.2.2}/js/tests/lightweight-chart.html +0 -0
  81. {spaday-0.2.0 → spaday-0.2.2}/js/tests/lightweight-chart.spec.js +0 -0
  82. {spaday-0.2.0 → spaday-0.2.2}/js/tests/runtime.html +0 -0
  83. {spaday-0.2.0 → spaday-0.2.2}/js/tests/runtime.spec.js +0 -0
  84. {spaday-0.2.0 → spaday-0.2.2}/js/tests/shell.spec.js +0 -0
  85. {spaday-0.2.0 → spaday-0.2.2}/js/tests/show.spec.js +0 -0
  86. {spaday-0.2.0 → spaday-0.2.2}/js/tests/store-sync.spec.js +0 -0
  87. {spaday-0.2.0 → spaday-0.2.2}/js/tests/widget-webawesome.html +0 -0
  88. {spaday-0.2.0 → spaday-0.2.2}/js/tests/widget-webawesome.spec.js +0 -0
  89. {spaday-0.2.0 → spaday-0.2.2}/js/tests/widget.html +0 -0
  90. {spaday-0.2.0 → spaday-0.2.2}/js/tests/widget.spec.js +0 -0
  91. {spaday-0.2.0 → spaday-0.2.2}/js/tools/bundle.mjs +0 -0
  92. {spaday-0.2.0 → spaday-0.2.2}/js/tools/css.mjs +0 -0
  93. {spaday-0.2.0 → spaday-0.2.2}/js/tools/externals.mjs +0 -0
  94. {spaday-0.2.0 → spaday-0.2.2}/js/tools/getarg.mjs +0 -0
  95. {spaday-0.2.0 → spaday-0.2.2}/js/tools/html.mjs +0 -0
  96. {spaday-0.2.0 → spaday-0.2.2}/js/tsconfig.json +0 -0
  97. {spaday-0.2.0 → spaday-0.2.2}/rust/.config/nextest.toml +0 -0
  98. {spaday-0.2.0 → spaday-0.2.2}/rust/Cargo.lock +0 -0
  99. {spaday-0.2.0 → spaday-0.2.2}/rust/python/example/mod.rs +0 -0
  100. {spaday-0.2.0 → spaday-0.2.2}/rust/python/lib.rs +0 -0
  101. {spaday-0.2.0 → spaday-0.2.2}/rust/python/patch/mod.rs +0 -0
  102. {spaday-0.2.0 → spaday-0.2.2}/rust/src/cem.rs +0 -0
  103. {spaday-0.2.0 → spaday-0.2.2}/rust/src/diff.rs +0 -0
  104. {spaday-0.2.0 → spaday-0.2.2}/rust/src/example.rs +0 -0
  105. {spaday-0.2.0 → spaday-0.2.2}/rust/src/json.rs +0 -0
  106. {spaday-0.2.0 → spaday-0.2.2}/rust/src/node.rs +0 -0
  107. {spaday-0.2.0 → spaday-0.2.2}/rust/src/value.rs +0 -0
  108. {spaday-0.2.0 → spaday-0.2.2}/rust/src/wire.rs +0 -0
  109. {spaday-0.2.0 → spaday-0.2.2}/spaday/backends/__init__.py +0 -0
  110. {spaday-0.2.0 → spaday-0.2.2}/spaday/cem.py +0 -0
  111. {spaday-0.2.0 → spaday-0.2.2}/spaday/component.py +0 -0
  112. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/__init__.py +0 -0
  113. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/form.py +0 -0
  114. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/lightweight_charts.cem.json +0 -0
  115. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/lightweight_charts.py +0 -0
  116. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/perspective.cem.json +0 -0
  117. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/perspective.py +0 -0
  118. {spaday-0.2.0 → spaday-0.2.2}/spaday/components/webawesome.py +0 -0
  119. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/README.md +0 -0
  120. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/__init__.py +0 -0
  121. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/__main__.py +0 -0
  122. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/cluster.py +0 -0
  123. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/devices.py +0 -0
  124. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/embed.py +0 -0
  125. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/form.py +0 -0
  126. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/fragment.py +0 -0
  127. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/gateway.py +0 -0
  128. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/reactive.py +0 -0
  129. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/ssr.py +0 -0
  130. {spaday-0.2.0 → spaday-0.2.2}/spaday/examples/widget.py +0 -0
  131. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/examples/gateway.js +0 -0
  132. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/examples/gateway.js.map +0 -0
  133. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/examples/webawesome.js +0 -0
  134. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/examples/webawesome.js.map +0 -0
  135. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/wrappers/lightweight-chart.js +0 -0
  136. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/wrappers/lightweight-chart.js.map +0 -0
  137. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/wrappers/perspective-workspace.js +0 -0
  138. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/cdn/wrappers/perspective-workspace.js.map +0 -0
  139. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/css/index.css +0 -0
  140. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/css/webawesome.css +0 -0
  141. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/index.html +0 -0
  142. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/pkg/spaday.d.ts +0 -0
  143. {spaday-0.2.0 → spaday-0.2.2}/spaday/extension/pkg/spaday_bg.wasm.d.ts +0 -0
  144. {spaday-0.2.0 → spaday-0.2.2}/spaday/render.py +0 -0
  145. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/fixtures/webawesome.3.9.0.cem.json +0 -0
  146. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/fixtures/webawesome.cem.json +0 -0
  147. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_all.py +0 -0
  148. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_backend_aiohttp.py +0 -0
  149. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_backend_flask.py +0 -0
  150. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_backend_tornado.py +0 -0
  151. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_bindings.py +0 -0
  152. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_cem.py +0 -0
  153. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_cluster.py +0 -0
  154. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_examples.py +0 -0
  155. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_form.py +0 -0
  156. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_gateway.py +0 -0
  157. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_imports.py +0 -0
  158. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_render.py +0 -0
  159. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_theme.py +0 -0
  160. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_widget.py +0 -0
  161. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_wire.py +0 -0
  162. {spaday-0.2.0 → spaday-0.2.2}/spaday/tests/test_wrappers.py +0 -0
  163. {spaday-0.2.0 → spaday-0.2.2}/spaday/theme.py +0 -0
  164. {spaday-0.2.0 → spaday-0.2.2}/spaday/widget.py +0 -0
@@ -84,9 +84,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
84
84
 
85
85
  [[package]]
86
86
  name = "memchr"
87
- version = "2.8.2"
87
+ version = "2.8.3"
88
88
  source = "registry+https://github.com/rust-lang/crates.io-index"
89
- checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
89
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
90
90
 
91
91
  [[package]]
92
92
  name = "num-traits"
@@ -184,9 +184,9 @@ dependencies = [
184
184
 
185
185
  [[package]]
186
186
  name = "quote"
187
- version = "1.0.45"
187
+ version = "1.0.46"
188
188
  source = "registry+https://github.com/rust-lang/crates.io-index"
189
- checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
189
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
190
190
  dependencies = [
191
191
  "proc-macro2",
192
192
  ]
@@ -212,9 +212,9 @@ dependencies = [
212
212
 
213
213
  [[package]]
214
214
  name = "rustversion"
215
- version = "1.0.22"
215
+ version = "1.0.23"
216
216
  source = "registry+https://github.com/rust-lang/crates.io-index"
217
- checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
217
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
218
218
 
219
219
  [[package]]
220
220
  name = "serde"
@@ -278,7 +278,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
278
278
 
279
279
  [[package]]
280
280
  name = "spaday"
281
- version = "0.2.0"
281
+ version = "0.2.2"
282
282
  dependencies = [
283
283
  "serde",
284
284
  "serde_json",
@@ -287,7 +287,7 @@ dependencies = [
287
287
 
288
288
  [[package]]
289
289
  name = "spaday_js"
290
- version = "0.2.0"
290
+ version = "0.2.2"
291
291
  dependencies = [
292
292
  "js-sys",
293
293
  "serde",
@@ -298,7 +298,7 @@ dependencies = [
298
298
 
299
299
  [[package]]
300
300
  name = "spaday_py"
301
- version = "0.2.0"
301
+ version = "0.2.2"
302
302
  dependencies = [
303
303
  "pyo3",
304
304
  "spaday",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spaday_py"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaday
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Build reactive web-component UIs configured in Python, executed in the browser
5
5
  Project-URL: Repository, https://github.com/1kbgz/spaday
6
6
  Project-URL: Homepage, https://github.com/1kbgz/spaday
@@ -10,7 +10,6 @@ License-File: LICENSE
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
16
15
  Classifier: Programming Language :: Python :: 3.13
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spaday_js"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  edition = "2021"
5
5
  publish = false
6
6
  keywords = ["experimental"]
@@ -108,6 +108,14 @@ async function build() {
108
108
  !file.relativePath.startsWith("esm/") &&
109
109
  !file.relativePath.startsWith("dist/esm/"),
110
110
  });
111
+ await cpy(
112
+ "node_modules/@1kbgz/transports/dist/cdn/index.js*",
113
+ "../spaday/extension/transports/cdn",
114
+ );
115
+ await cpy(
116
+ "node_modules/@1kbgz/transports/dist/pkg/*",
117
+ "../spaday/extension/transports/pkg",
118
+ );
111
119
  }
112
120
 
113
121
  build();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaday",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "",
5
5
  "repository": "git@github.com:1kbgz/spaday.git",
6
6
  "author": "1kbgz <dev@1kbgz.com>",
@@ -25,7 +25,7 @@ extern "C" {
25
25
  #[wasm_bindgen(method, js_name = sendPatch)]
26
26
  fn send_patch(this: &Host, model: &str, field: &str, value: JsValue);
27
27
  #[wasm_bindgen(method, js_name = callEndpoint)]
28
- fn call_endpoint(this: &Host, method: &str, url: &str, body: JsValue, result: Option<&str>);
28
+ fn call_endpoint(this: &Host, method: &str, url: JsValue, body: JsValue, result: Option<&str>);
29
29
  #[wasm_bindgen(method, js_name = callNamed)]
30
30
  fn call_named(this: &Host, handler: &str);
31
31
  }
@@ -100,15 +100,19 @@ fn run(action: &spaday::Action, host: &Host) {
100
100
  body,
101
101
  result,
102
102
  } => {
103
+ let u = match url {
104
+ spaday::EndpointUrl::Static(value) => JsValue::from_str(value),
105
+ spaday::EndpointUrl::Expr(expr) => eval(expr, host),
106
+ };
103
107
  let b = body.as_ref().map_or(JsValue::UNDEFINED, |e| eval(e, host));
104
- host.call_endpoint(method, url, b, result.as_deref());
108
+ host.call_endpoint(method, u, b, result.as_deref());
105
109
  }
106
110
  NamedJs { handler } => host.call_named(handler),
107
111
  }
108
112
  }
109
113
 
110
114
  fn eval(expr: &spaday::Expr, host: &Host) -> JsValue {
111
- use spaday::Expr::{Event, Field, Lit, Not, Obj, Prop};
115
+ use spaday::Expr::{Concat, Event, Field, Lit, Not, Obj, Prop};
112
116
  match expr {
113
117
  // json_compatible: JSON objects become plain JS objects (not Maps), so they round-trip through
114
118
  // `JSON.stringify` (e.g. a CallEndpoint body) and set cleanly as props.
@@ -130,6 +134,13 @@ fn eval(expr: &spaday::Expr, host: &Host) -> JsValue {
130
134
  }
131
135
  obj.into()
132
136
  }
137
+ Concat { parts } => {
138
+ let values = js_sys::Array::new();
139
+ for part in parts {
140
+ values.push(&eval(part, host));
141
+ }
142
+ values.join("").into()
143
+ }
133
144
  }
134
145
  }
135
146
 
@@ -68,11 +68,11 @@ function host(ctx: ActionContext) {
68
68
  // UI, e.g. show a 422 validation error); without one it stays fire-and-forget.
69
69
  callEndpoint: (
70
70
  method: string,
71
- url: string,
71
+ url: unknown,
72
72
  body: unknown,
73
73
  result?: string,
74
74
  ) => {
75
- const request = fetch(url, {
75
+ const request = fetch(String(url), {
76
76
  method,
77
77
  headers:
78
78
  body === undefined
@@ -126,6 +126,10 @@ export function evalExpr(expr: unknown, store: Store): unknown {
126
126
  out[k] = evalExpr(v, store);
127
127
  return out;
128
128
  }
129
+ case "concat":
130
+ return (e.parts as unknown[])
131
+ .map((part) => String(evalExpr(part, store)))
132
+ .join("");
129
133
  default:
130
134
  return undefined;
131
135
  }
@@ -357,6 +357,43 @@ test("CallEndpoint composes a body from the signal store via field exprs", async
357
357
  expect(JSON.parse(seen[0])).toEqual({ symbol: "AAPL", qty: 250 });
358
358
  });
359
359
 
360
+ test("CallEndpoint composes its URL from signal-store fields", async ({
361
+ page,
362
+ }) => {
363
+ const seen = [];
364
+ await page.route("**/send/basket/*", (route) => {
365
+ seen.push(new URL(route.request().url()).pathname);
366
+ return route.fulfill({ status: 200, body: "ok" });
367
+ });
368
+ await page.evaluate(() => {
369
+ const { mount, Store } = window.__spaday;
370
+ const store = new Store({ key: "B" });
371
+ const btn = mount(
372
+ document.body,
373
+ {
374
+ tag: "button",
375
+ events: {
376
+ click: {
377
+ kind: "call",
378
+ method: "POST",
379
+ url: {
380
+ expr: "concat",
381
+ parts: [
382
+ { expr: "lit", value: "/send/basket/" },
383
+ { expr: "field", name: "key" },
384
+ ],
385
+ },
386
+ },
387
+ },
388
+ },
389
+ store,
390
+ );
391
+ btn.click();
392
+ });
393
+ await page.waitForTimeout(150);
394
+ expect(seen).toEqual(["/send/basket/B"]);
395
+ });
396
+
360
397
  test("SetField writes a store field (a plain button drives reactive state)", async ({
361
398
  page,
362
399
  }) => {
@@ -234,6 +234,38 @@ test("a cond expr selects between two values by a field, reactively", async ({
234
234
  expect(result.after).toBe("dark");
235
235
  });
236
236
 
237
+ test("a concat expr composes strings from fields reactively", async ({
238
+ page,
239
+ }) => {
240
+ const result = await page.evaluate(() => {
241
+ const { mount, Store } = window.__spaday;
242
+ const store = new Store({ key: "A" });
243
+ const root = mount(
244
+ document.createElement("div"),
245
+ {
246
+ tag: "span",
247
+ bindings: {
248
+ textContent: {
249
+ compute: {
250
+ expr: "concat",
251
+ parts: [
252
+ { expr: "lit", value: "Basket " },
253
+ { expr: "field", name: "key" },
254
+ ],
255
+ },
256
+ mode: "one-way",
257
+ },
258
+ },
259
+ },
260
+ store,
261
+ );
262
+ const initial = root.textContent;
263
+ store.set("key", "B");
264
+ return { initial, after: root.textContent };
265
+ });
266
+ expect(result).toEqual({ initial: "Basket A", after: "Basket B" });
267
+ });
268
+
237
269
  test("a root-class binding toggles a class on <html> from a field", async ({
238
270
  page,
239
271
  }) => {
@@ -14,7 +14,7 @@ authors = [
14
14
  description = "Build reactive web-component UIs configured in Python, executed in the browser"
15
15
  readme = "README.md"
16
16
  license = { text = "Apache-2.0" }
17
- version = "0.2.0"
17
+ version = "0.2.2"
18
18
  requires-python = ">=3.10"
19
19
  keywords = []
20
20
 
@@ -25,7 +25,6 @@ classifiers = [
25
25
  "Programming Language :: Python :: Implementation :: CPython",
26
26
  "Programming Language :: Python :: Implementation :: PyPy",
27
27
  "Programming Language :: Python :: 3",
28
- "Programming Language :: Python :: 3.10",
29
28
  "Programming Language :: Python :: 3.11",
30
29
  "Programming Language :: Python :: 3.12",
31
30
  "Programming Language :: Python :: 3.13",
@@ -79,7 +78,7 @@ Repository = "https://github.com/1kbgz/spaday"
79
78
  Homepage = "https://github.com/1kbgz/spaday"
80
79
 
81
80
  [tool.bumpversion]
82
- current_version = "0.2.0"
81
+ current_version = "0.2.2"
83
82
  commit = true
84
83
  tag = false
85
84
  commit_args = "-s"
@@ -115,7 +114,7 @@ search = 'version = "{current_version}"'
115
114
  replace = 'version = "{new_version}"'
116
115
 
117
116
  [tool.cibuildwheel]
118
- build = "cp310-*"
117
+ build = "cp311-*"
119
118
  test-command = "pytest -vvv {project}/spaday/tests"
120
119
  test-extras = "develop"
121
120
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spaday"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  authors = ["1kbgz <dev@1kbgz.com>"]
5
5
  edition = "2021"
6
6
  license = "Apache-2.0"