mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 07:52:34 -05:00
Compare commits
700 Commits
v0.7.0-bet
...
2182
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1632efa09 | ||
|
|
d475b213d7 | ||
|
|
b932bd5e04 | ||
|
|
a0638b786c | ||
|
|
1033133d3a | ||
|
|
ba40560ad7 | ||
|
|
78dc8b4410 | ||
|
|
c6bf525270 | ||
|
|
fcdfd617f5 | ||
|
|
d9c8d4ff66 | ||
|
|
621f112f4c | ||
|
|
cfe2341dec | ||
|
|
5ed2cc9596 | ||
|
|
47b07b0143 | ||
|
|
0793d56648 | ||
|
|
2dde9738b2 | ||
|
|
1b276e1e02 | ||
|
|
9771619b0d | ||
|
|
382a36406c | ||
|
|
a898d3f2f7 | ||
|
|
951f4a73ee | ||
|
|
2a26a648ba | ||
|
|
66d7cb5d12 | ||
|
|
aa49ad760b | ||
|
|
3469e9335c | ||
|
|
62408d9202 | ||
|
|
db1c15f4e4 | ||
|
|
3f751551a8 | ||
|
|
2470036f57 | ||
|
|
e01dfbf497 | ||
|
|
49c1661f92 | ||
|
|
b9dfd9a5ae | ||
|
|
17a150b3bf | ||
|
|
c860f524ad | ||
|
|
20af4928b2 | ||
|
|
7b62ad44d2 | ||
|
|
5657abc07d | ||
|
|
9fc351ceac | ||
|
|
893d47f1c5 | ||
|
|
3c2a2304e4 | ||
|
|
3de4b7b982 | ||
|
|
36957cb569 | ||
|
|
27dbadb7d2 | ||
|
|
84590b98ed | ||
|
|
0222182286 | ||
|
|
7ed4d08dab | ||
|
|
4fa2e58551 | ||
|
|
5fe58369f5 | ||
|
|
10860ebb1e | ||
|
|
887eb99cf6 | ||
|
|
9f99571b28 | ||
|
|
b3e2040ec9 | ||
|
|
693861434c | ||
|
|
d1248d3faf | ||
|
|
f1fae63064 | ||
|
|
f7053ac960 | ||
|
|
8670365594 | ||
|
|
e0cc6fd7b9 | ||
|
|
84457b45ff | ||
|
|
8c959d3e24 | ||
|
|
288da232ac | ||
|
|
ef52e8620b | ||
|
|
a8c5ce2722 | ||
|
|
a40ba9a39e | ||
|
|
83a110fb85 | ||
|
|
5deedf721e | ||
|
|
0ae67cf122 | ||
|
|
97e0222061 | ||
|
|
a7aedee4fd | ||
|
|
f180941b7b | ||
|
|
83cb3cb764 | ||
|
|
f174688974 | ||
|
|
1f4c410f78 | ||
|
|
3dbedfc871 | ||
|
|
026152d20e | ||
|
|
c70009243a | ||
|
|
dcdad73476 | ||
|
|
42a8e49e1a | ||
|
|
9ba894764e | ||
|
|
1d97494e19 | ||
|
|
982bfd8011 | ||
|
|
a0ad927097 | ||
|
|
4845459511 | ||
|
|
54e4205541 | ||
|
|
25f0186098 | ||
|
|
00fb8f29d3 | ||
|
|
82ea4eb7ce | ||
|
|
34382c0c23 | ||
|
|
605e4b1eec | ||
|
|
71ca02a432 | ||
|
|
ac751dd0d3 | ||
|
|
114df8797c | ||
|
|
35b457aa82 | ||
|
|
c43b71d3fa | ||
|
|
5ad17d6b6c | ||
|
|
805be42f7a | ||
|
|
bb63a1521e | ||
|
|
05cd1bc6f0 | ||
|
|
84ac64284c | ||
|
|
8beb988ecc | ||
|
|
05999069b7 | ||
|
|
dbb4c79d9c | ||
|
|
d1d6126259 | ||
|
|
0e979a0767 | ||
|
|
3a4ad07a91 | ||
|
|
f2f35cd785 | ||
|
|
c953432659 | ||
|
|
27f2e60d16 | ||
|
|
9330cf23b1 | ||
|
|
04f5207457 | ||
|
|
c88409a333 | ||
|
|
a2d3d9431c | ||
|
|
213365e4e9 | ||
|
|
ab3e94dafa | ||
|
|
3e05b5bcb4 | ||
|
|
632ce0f401 | ||
|
|
93c893f4b3 | ||
|
|
4fa72a94fb | ||
|
|
24775fb59b | ||
|
|
6c749f5e24 | ||
|
|
c22f20ac28 | ||
|
|
11011c2bda | ||
|
|
a51faea9a9 | ||
|
|
dcc43e574b | ||
|
|
6cc0604497 | ||
|
|
8c375534bb | ||
|
|
f2d6375d93 | ||
|
|
5fb80aaa40 | ||
|
|
29b0dca1d8 | ||
|
|
a6a65ba562 | ||
|
|
260c624461 | ||
|
|
0f50aced26 | ||
|
|
1aa2752842 | ||
|
|
b92bfa4ea7 | ||
|
|
30cf1167f2 | ||
|
|
1c05389707 | ||
|
|
1534dd5261 | ||
|
|
8b9685e01d | ||
|
|
d9b590b8e0 | ||
|
|
25bfc27544 | ||
|
|
89bbdc58af | ||
|
|
4fb00e29d6 | ||
|
|
54401c6f69 | ||
|
|
6a705e2a21 | ||
|
|
43421c56d5 | ||
|
|
4c4d3dcfa3 | ||
|
|
55053da00c | ||
|
|
be83b5f27e | ||
|
|
a4ce79769a | ||
|
|
e3f64188c2 | ||
|
|
f7abe727d9 | ||
|
|
eb29d84169 | ||
|
|
200047a8bc | ||
|
|
634ac1c4a3 | ||
|
|
efe832e39a | ||
|
|
1f2b13a976 | ||
|
|
d4ec5e187b | ||
|
|
4fe7fe725f | ||
|
|
a1ca8549a1 | ||
|
|
e7a8067f9b | ||
|
|
6be090079f | ||
|
|
8635887ca7 | ||
|
|
e3482b433b | ||
|
|
e6c2f8c614 | ||
|
|
28fcfe4a46 | ||
|
|
75336bc265 | ||
|
|
f4f129caaf | ||
|
|
873aec5787 | ||
|
|
a2385e4c42 | ||
|
|
d24f97b59f | ||
|
|
51f368c5c5 | ||
|
|
4107203da2 | ||
|
|
efb699a319 | ||
|
|
93e6456e19 | ||
|
|
b24ae7a5e3 | ||
|
|
bf8d2e079c | ||
|
|
7752ab78e3 | ||
|
|
64bc2580ff | ||
|
|
ddb596feb5 | ||
|
|
dac4589194 | ||
|
|
8f0a8e05b4 | ||
|
|
05d01141c5 | ||
|
|
66d6038f2d | ||
|
|
3b09312e1a | ||
|
|
62cb361031 | ||
|
|
04c67cb8b6 | ||
|
|
efd060c955 | ||
|
|
6290c42159 | ||
|
|
0a89f151be | ||
|
|
c72c2f4803 | ||
|
|
c771ab7e71 | ||
|
|
2c4f11b238 | ||
|
|
12a9e06c5e | ||
|
|
3515469835 | ||
|
|
e5c159f7a5 | ||
|
|
6590749956 | ||
|
|
db8f5e4899 | ||
|
|
6ca3639c3e | ||
|
|
37db7b5d0a | ||
|
|
8ac1564b90 | ||
|
|
e2721d53bd | ||
|
|
e1f3be6416 | ||
|
|
9536480739 | ||
|
|
5d3a1752c4 | ||
|
|
4b539b524b | ||
|
|
67fe4cc540 | ||
|
|
fa731d5018 | ||
|
|
ccf6703274 | ||
|
|
504c958001 | ||
|
|
f7b16b726b | ||
|
|
e9c7b50dfd | ||
|
|
208ab97867 | ||
|
|
4a0f173bb5 | ||
|
|
0cf3113812 | ||
|
|
87f9fa23d5 | ||
|
|
746bf8e453 | ||
|
|
8b9bcffbb9 | ||
|
|
dc80e387e3 | ||
|
|
2006eca1a0 | ||
|
|
1dae77d6b4 | ||
|
|
2f58191a56 | ||
|
|
a68653b385 | ||
|
|
d7ca969848 | ||
|
|
fd48a61eef | ||
|
|
52a3f84de5 | ||
|
|
f8283f4674 | ||
|
|
989f2989fa | ||
|
|
33a3708f91 | ||
|
|
4eea1f046d | ||
|
|
8f46288973 | ||
|
|
059c8abd2f | ||
|
|
6885777c75 | ||
|
|
ddc7abf081 | ||
|
|
180511e9bb | ||
|
|
381ff8a7b0 | ||
|
|
3ed1ad7b7f | ||
|
|
2ccf5e99a9 | ||
|
|
055701ebf6 | ||
|
|
88af893703 | ||
|
|
ce4fe632a2 | ||
|
|
c76208aad0 | ||
|
|
514c51ca30 | ||
|
|
7e3781b5dd | ||
|
|
97dc3cc2e5 | ||
|
|
61e51cbe1c | ||
|
|
efa6d603f9 | ||
|
|
da045f7358 | ||
|
|
8502745036 | ||
|
|
0a5e6fd85a | ||
|
|
64fc6cd514 | ||
|
|
a2d8fde8cf | ||
|
|
44eae4c2ed | ||
|
|
38d51b01d7 | ||
|
|
61876dff10 | ||
|
|
c676cf921d | ||
|
|
fc59cdaf61 | ||
|
|
081f4ec550 | ||
|
|
598c59b9c2 | ||
|
|
9de6c5bb4a | ||
|
|
f65eaec9ba | ||
|
|
95756aa2f7 | ||
|
|
fd121fd8c1 | ||
|
|
c1877354f0 | ||
|
|
be92dc56aa | ||
|
|
165a593b32 | ||
|
|
18b33c7606 | ||
|
|
d2ee093132 | ||
|
|
83e0438527 | ||
|
|
095dc78893 | ||
|
|
3ebea79e05 | ||
|
|
fe7c7c3a99 | ||
|
|
8b142c72f0 | ||
|
|
70655b57b1 | ||
|
|
c6192badfb | ||
|
|
5b7f5e3db3 | ||
|
|
ae14644806 | ||
|
|
7ca810d8bd | ||
|
|
04e09d2005 | ||
|
|
2916873985 | ||
|
|
2a558aa3f0 | ||
|
|
36d16d9253 | ||
|
|
722fd0f6c2 | ||
|
|
a42e371e79 | ||
|
|
11119144d2 | ||
|
|
a0b158f016 | ||
|
|
8dc7338b85 | ||
|
|
737949cff6 | ||
|
|
d7e17a2ec9 | ||
|
|
7c5b7fcbb1 | ||
|
|
1182aff410 | ||
|
|
bdcd4cb1cc | ||
|
|
c74a791d9f | ||
|
|
772a837050 | ||
|
|
92552deb0d | ||
|
|
417d345b83 | ||
|
|
3fb2d49d89 | ||
|
|
27feaf4309 | ||
|
|
35f489a52e | ||
|
|
ba8bd2bc82 | ||
|
|
76506c03e1 | ||
|
|
4323e30133 | ||
|
|
81c0947ce5 | ||
|
|
309a3d504a | ||
|
|
2a236e043a | ||
|
|
63f8da2fb5 | ||
|
|
c9e32b66bf | ||
|
|
a32c71539d | ||
|
|
f7ee0c4764 | ||
|
|
7034375cdd | ||
|
|
3c9c5aaf83 | ||
|
|
ce832cef21 | ||
|
|
10230d6d65 | ||
|
|
e4d25608df | ||
|
|
bd1601e892 | ||
|
|
602ac60a85 | ||
|
|
9e4c0b86f2 | ||
|
|
4a80c8b65b | ||
|
|
f191bb8324 | ||
|
|
1ff1d48e6e | ||
|
|
df6a4628c3 | ||
|
|
e28e5ceb1e | ||
|
|
e69f62b939 | ||
|
|
2c48b07186 | ||
|
|
0d867ba016 | ||
|
|
3f83ad7dda | ||
|
|
50403846c9 | ||
|
|
4ead16e5d3 | ||
|
|
32f77cc42b | ||
|
|
d8834a0423 | ||
|
|
0d665c9c05 | ||
|
|
a03d74494d | ||
|
|
131c83e28e | ||
|
|
6d93185478 | ||
|
|
202abd1d35 | ||
|
|
a50c6b0140 | ||
|
|
705ea3a3bb | ||
|
|
cb788758df | ||
|
|
1afdc4fe1e | ||
|
|
3382047857 | ||
|
|
a29ffc8dcb | ||
|
|
a18dd6dfd7 | ||
|
|
626bcdc9ae | ||
|
|
d6dce76725 | ||
|
|
36272a0b1b | ||
|
|
96c956efdf | ||
|
|
29cf1f4814 | ||
|
|
39c3a63787 | ||
|
|
068865b7de | ||
|
|
fa8bb15a67 | ||
|
|
faa481f2b6 | ||
|
|
b41d988865 | ||
|
|
025c28b489 | ||
|
|
0c7c7c9b38 | ||
|
|
b109c3e9a3 | ||
|
|
0a559935e7 | ||
|
|
bccc05fec8 | ||
|
|
e0f98dc0fd | ||
|
|
5d9bd8f913 | ||
|
|
0a41ae9a5e | ||
|
|
fbc6be922d | ||
|
|
b5551863fe | ||
|
|
14b3877293 | ||
|
|
98ea18009d | ||
|
|
d133cff092 | ||
|
|
48028b476a | ||
|
|
404ad50bd3 | ||
|
|
b89fbe027b | ||
|
|
0ba53afa08 | ||
|
|
c384b53a0f | ||
|
|
2f53e09bb6 | ||
|
|
319eefb169 | ||
|
|
949f43d145 | ||
|
|
a47759007f | ||
|
|
095faf15b1 | ||
|
|
f9eb562050 | ||
|
|
7f57b88e8d | ||
|
|
8a8862be9e | ||
|
|
619dc59e1d | ||
|
|
5f49504137 | ||
|
|
ca68fa5a3d | ||
|
|
e6a472b467 | ||
|
|
f8da9e30e0 | ||
|
|
984ede8887 | ||
|
|
c3656416a2 | ||
|
|
7ecfbd9109 | ||
|
|
531c39759a | ||
|
|
f5d06577f4 | ||
|
|
39902d1e66 | ||
|
|
7def5f65ed | ||
|
|
6b60d48203 | ||
|
|
9ef51166d3 | ||
|
|
8da6bbc3be | ||
|
|
3c39674622 | ||
|
|
914b07491e | ||
|
|
1d2d11b83d | ||
|
|
07e878adf7 | ||
|
|
f32d43ce94 | ||
|
|
65e3c57ed1 | ||
|
|
2e40bace88 | ||
|
|
b9945e0ce1 | ||
|
|
d7f70214b9 | ||
|
|
adf57f5771 | ||
|
|
bae79e2b2c | ||
|
|
e2b1210461 | ||
|
|
7c24b7482d | ||
|
|
25c66a4624 | ||
|
|
71ddacef8e | ||
|
|
338b01bee3 | ||
|
|
a36f22e439 | ||
|
|
56977411f2 | ||
|
|
0fc47e3a35 | ||
|
|
caf797dba0 | ||
|
|
2f54d937a1 | ||
|
|
40c1f38a07 | ||
|
|
402d6297f4 | ||
|
|
93734a5222 | ||
|
|
770d02d8e6 | ||
|
|
e275862a20 | ||
|
|
17f1d25d03 | ||
|
|
0a99a378aa | ||
|
|
14b7073863 | ||
|
|
4e4deef144 | ||
|
|
c360f0ed0d | ||
|
|
88ab9693db | ||
|
|
6dfea0b0a2 | ||
|
|
9fd881603f | ||
|
|
9666c9c0c5 | ||
|
|
9e8b304b8a | ||
|
|
064ccce5b1 | ||
|
|
2e31177f62 | ||
|
|
4215cef04b | ||
|
|
de3dd3c296 | ||
|
|
846ff2fefb | ||
|
|
6003212f6e | ||
|
|
054cff7883 | ||
|
|
ce5738d7c4 | ||
|
|
47331b5c8d | ||
|
|
a6cee3b1e9 | ||
|
|
43c0e384c4 | ||
|
|
db654cbfda | ||
|
|
e13b1561d8 | ||
|
|
02f76dec35 | ||
|
|
4bd99a41e5 | ||
|
|
85d29a5af5 | ||
|
|
4d54574f9e | ||
|
|
f6c7ac473a | ||
|
|
747d847183 | ||
|
|
8dd63a402b | ||
|
|
694eccbadc | ||
|
|
24f2e71563 | ||
|
|
1766bfedb9 | ||
|
|
242d35cc37 | ||
|
|
85b9f87620 | ||
|
|
db33bc2e61 | ||
|
|
a1329ea044 | ||
|
|
050bf8f821 | ||
|
|
1a68743fcc | ||
|
|
2925db8676 | ||
|
|
13d5f12d7f | ||
|
|
3d9c295613 | ||
|
|
b2c0068e2c | ||
|
|
94a3f7c092 | ||
|
|
330dcfeb7c | ||
|
|
f7bbec5f06 | ||
|
|
8815529955 | ||
|
|
12db58a7e0 | ||
|
|
83c9edde26 | ||
|
|
2037a6966a | ||
|
|
4f041f5a5e | ||
|
|
6467e067ef | ||
|
|
3814879d80 | ||
|
|
5e16ae6a26 | ||
|
|
6d474713f6 | ||
|
|
0d47399424 | ||
|
|
ae254836d7 | ||
|
|
2dd5efc5d0 | ||
|
|
15eeda9c7a | ||
|
|
1a739015e1 | ||
|
|
8385287123 | ||
|
|
c4aa3ba1ba | ||
|
|
ce5f2c81ed | ||
|
|
941689fc5b | ||
|
|
961bf89a8b | ||
|
|
d360cc280f | ||
|
|
bb7bb8f4c2 | ||
|
|
b29b8fb5ff | ||
|
|
4ffa3c46b6 | ||
|
|
32294d6cab | ||
|
|
46d286755e | ||
|
|
b936e0352f | ||
|
|
b5bd70ab94 | ||
|
|
0dd1932b7f | ||
|
|
f5d203f0c9 | ||
|
|
1bc0b414e3 | ||
|
|
d6e19c0a60 | ||
|
|
fc60d6b2d7 | ||
|
|
292e7c1f27 | ||
|
|
1da84db1aa | ||
|
|
535e3e3880 | ||
|
|
109244b28b | ||
|
|
fd048295a4 | ||
|
|
26cf4848db | ||
|
|
757a5c73c3 | ||
|
|
da496def16 | ||
|
|
3a755bd8c3 | ||
|
|
e514f7144d | ||
|
|
b881167b8f | ||
|
|
1e9d345831 | ||
|
|
7f7bba6ea3 | ||
|
|
015a4b63ec | ||
|
|
dcec7af4f3 | ||
|
|
5bc97654dc | ||
|
|
2788d93e96 | ||
|
|
604043b4d8 | ||
|
|
ab28c80593 | ||
|
|
49da073fed | ||
|
|
3629302f88 | ||
|
|
274e31018b | ||
|
|
802fcc5c2a | ||
|
|
da084a2ece | ||
|
|
d9f6836933 | ||
|
|
d8d2fdac5d | ||
|
|
9818e7cb68 | ||
|
|
986fbe5328 | ||
|
|
711175a760 | ||
|
|
00a536a5dc | ||
|
|
a7b1152910 | ||
|
|
cfba7a2797 | ||
|
|
cebe744a84 | ||
|
|
e93a34a2c9 | ||
|
|
9ec30d71d2 | ||
|
|
3c13280bf6 | ||
|
|
45fd9423f8 | ||
|
|
7a92208c4f | ||
|
|
89b972e3c5 | ||
|
|
8dac92b251 | ||
|
|
b24eaedfe9 | ||
|
|
4336051f78 | ||
|
|
97ce5adb8e | ||
|
|
20fb5454b0 | ||
|
|
aac607f338 | ||
|
|
738986415d | ||
|
|
3406446ebd | ||
|
|
21dd7e9c76 | ||
|
|
9bab4da172 | ||
|
|
420dccda60 | ||
|
|
53b22a9b74 | ||
|
|
e68730d15f | ||
|
|
11d134c4ba | ||
|
|
2239f04f6b | ||
|
|
78e5a7ebc3 | ||
|
|
0148d92f48 | ||
|
|
ab67bea7ec | ||
|
|
0beef3b2e0 | ||
|
|
81fc7e6ada | ||
|
|
75d6763f4e | ||
|
|
da4d2cf538 | ||
|
|
2470637b0b | ||
|
|
2934c295b5 | ||
|
|
789eef914d | ||
|
|
782cb93743 | ||
|
|
8642c563d8 | ||
|
|
755fbd3866 | ||
|
|
d83471e02b | ||
|
|
2dd2bb5958 | ||
|
|
12f2cec5c7 | ||
|
|
a41bf2784f | ||
|
|
ebdd31cd9f | ||
|
|
acec3bb313 | ||
|
|
464f157186 | ||
|
|
b53e4d8ff8 | ||
|
|
cd438e0bcf | ||
|
|
13da1e743d | ||
|
|
0c9167fd30 | ||
|
|
52da0e43ac | ||
|
|
dad91f5960 | ||
|
|
72e97047a5 | ||
|
|
883fd57fe1 | ||
|
|
42b99dd912 | ||
|
|
851e1f73fd | ||
|
|
e11eea1af1 | ||
|
|
f508cc4510 | ||
|
|
e4f3cf9cca | ||
|
|
60d883a26c | ||
|
|
add3be0ff5 | ||
|
|
a01640cafd | ||
|
|
e837e9fded | ||
|
|
e0e67360aa | ||
|
|
439deea066 | ||
|
|
e5f5710f46 | ||
|
|
8626db27d7 | ||
|
|
ec3f0933fe | ||
|
|
b50de3a005 | ||
|
|
aa878534ad | ||
|
|
603f9f96c4 | ||
|
|
f78e675506 | ||
|
|
cc2714c03d | ||
|
|
c06110128b | ||
|
|
d7c62622ae | ||
|
|
1edec6c36a | ||
|
|
c5049ca1bb | ||
|
|
f69dbb48ca | ||
|
|
5feaf1aea6 | ||
|
|
ec3ab6a355 | ||
|
|
100ed7d926 | ||
|
|
88b93f40f9 | ||
|
|
b8b77138ea | ||
|
|
20c29cab89 | ||
|
|
54fd74839a | ||
|
|
ea3790d91c | ||
|
|
f5935c6333 | ||
|
|
c8e5e1b16b | ||
|
|
a12c707f3f | ||
|
|
6d9906111d | ||
|
|
5ea314c998 | ||
|
|
2bc04444e1 | ||
|
|
b41fde3ff9 | ||
|
|
c29081b12a | ||
|
|
2fefc8b4bf | ||
|
|
72b43d1e2b | ||
|
|
39607adc94 | ||
|
|
30c1cd921b | ||
|
|
abfe3cabd2 | ||
|
|
16bd2942db | ||
|
|
13cccced06 | ||
|
|
db4c1cb4b3 | ||
|
|
9cdd8cac15 | ||
|
|
84ebdc1b92 | ||
|
|
9f02cc8cc1 | ||
|
|
c3b9932172 | ||
|
|
dbd9951a85 | ||
|
|
6eb8b44fff | ||
|
|
4fa31be5dc | ||
|
|
b46dffb729 | ||
|
|
ca54762806 | ||
|
|
f122f9109f | ||
|
|
f894d6e4f6 | ||
|
|
4cc925c950 | ||
|
|
21e53042e8 | ||
|
|
4d3fb37b35 | ||
|
|
d3a21c922d | ||
|
|
317f90e1e3 | ||
|
|
26869a78a0 | ||
|
|
f46f864f05 | ||
|
|
b21f1853c6 | ||
|
|
1454c5d272 | ||
|
|
c1f4616a31 | ||
|
|
a3c3478831 | ||
|
|
1ca8a9189c | ||
|
|
9e276a8879 | ||
|
|
53703f208a | ||
|
|
9a60b21a0a | ||
|
|
524ed395fa | ||
|
|
5bc8c4e0d3 | ||
|
|
7f7003f7f1 | ||
|
|
ddf2ac0cf7 | ||
|
|
992e2bce78 | ||
|
|
6c2469ec3a | ||
|
|
a7162d7907 | ||
|
|
f584154156 | ||
|
|
13464b10c9 | ||
|
|
696bf14d13 | ||
|
|
be92bab3e5 | ||
|
|
4bb2bc4797 | ||
|
|
a8adf8eea2 | ||
|
|
1a7da39fb7 | ||
|
|
201adb7406 | ||
|
|
4df42cbc60 | ||
|
|
44a0a0a93a | ||
|
|
66e1e6d7a1 | ||
|
|
8252c4a977 | ||
|
|
6a2eafcbc6 | ||
|
|
b49a13f8c1 | ||
|
|
0d5c67408f | ||
|
|
1eddd5a5f1 | ||
|
|
ca1e62c0b9 | ||
|
|
043cd7dc61 | ||
|
|
68486cfb72 | ||
|
|
eea971b9fe | ||
|
|
d726b56b71 | ||
|
|
0fddfb4823 | ||
|
|
17732a6e6a | ||
|
|
c8441f0f00 | ||
|
|
ff4cde0764 | ||
|
|
1d38439bd8 | ||
|
|
9ca1cba504 | ||
|
|
63dacdcc95 | ||
|
|
61f5294f67 | ||
|
|
0149632a4c | ||
|
|
96384ed116 | ||
|
|
f56023bb25 | ||
|
|
6bb5d58369 | ||
|
|
d50012f8d4 | ||
|
|
c9d4ea9307 | ||
|
|
77c74bccbb | ||
|
|
528d1eae65 | ||
|
|
5809c8f699 | ||
|
|
b9c620d4cd | ||
|
|
8c9dfd9c9d | ||
|
|
8848eb8b87 |
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
directories:
|
||||
- "/"
|
||||
- "/examples/*"
|
||||
- "/benchmarks"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
6
.github/workflows/ci-semver.yml
vendored
6
.github/workflows/ci-semver.yml
vendored
@@ -14,8 +14,8 @@ jobs:
|
||||
|
||||
test:
|
||||
needs: [get-leptos-changed]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-04-14)
|
||||
if: github.event.pull_request.labels[0].name == 'semver' # needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-08-01)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -25,4 +25,4 @@ jobs:
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: nightly-2024-04-14
|
||||
rust-toolchain: nightly-2024-08-01
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -49,4 +49,4 @@ jobs:
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly-2024-04-14
|
||||
toolchain: nightly-2024-08-01
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Get example project directories that changed
|
||||
id: changed-dirs
|
||||
uses: tj-actions/changed-files@v41
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
|
||||
2
.github/workflows/get-example-changed.yml
vendored
2
.github/workflows/get-example-changed.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
- name: Get example files that changed
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v43
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
files: |
|
||||
examples/**
|
||||
|
||||
2
.github/workflows/get-leptos-changed.yml
vendored
2
.github/workflows/get-leptos-changed.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
- name: Get source files that changed
|
||||
id: changed-source
|
||||
uses: tj-actions/changed-files@v43
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
files: |
|
||||
any_error/**
|
||||
|
||||
2
.github/workflows/run-cargo-make-task.yml
vendored
2
.github/workflows/run-cargo-make-task.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
|
||||
56
Cargo.toml
56
Cargo.toml
@@ -37,37 +37,39 @@ members = [
|
||||
"router_macro",
|
||||
"any_error",
|
||||
]
|
||||
exclude = ["benchmarks", "examples"]
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-alpha"
|
||||
rust-version = "1.75"
|
||||
version = "0.7.0-beta2"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.1" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-alpha" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-alpha" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-alpha" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-alpha" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-alpha" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-alpha" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-alpha" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-alpha" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-alpha" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-alpha" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-alpha" }
|
||||
oco_ref = { path = "./oco", version = "0.2" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-alpha" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-alpha" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-alpha" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-alpha" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-alpha" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-alpha" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-alpha" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta2" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta2" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta2" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta2" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta2" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta2" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta2" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta2" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta2" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta2" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta2" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
You can find a list of useful libraries and example projects at [`awesome-leptos`](https://github.com/leptos-rs/awesome-leptos).
|
||||
|
||||
# The `main` branch is currently undergoing major changes in preparation for the [0.7](https://github.com/leptos-rs/leptos/milestone/4) release. For a stable version, please use the [v0.6.13 tag](https://github.com/leptos-rs/leptos/tree/v0.6.13)
|
||||
|
||||
# Leptos
|
||||
|
||||
```rust
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0-beta2"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for wrapping, throwing, and catching errors."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2"
|
||||
pin-project-lite = "0.2.14"
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
error,
|
||||
fmt::{self, Display},
|
||||
future::Future,
|
||||
ops,
|
||||
mem, ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
@@ -92,9 +92,25 @@ thread_local! {
|
||||
static ERROR_HOOK: RefCell<Option<Arc<dyn ErrorHook>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Resets the error hook to its previous state when dropped.
|
||||
pub struct ResetErrorHookOnDrop(Option<Arc<dyn ErrorHook>>);
|
||||
|
||||
impl Drop for ResetErrorHookOnDrop {
|
||||
fn drop(&mut self) {
|
||||
ERROR_HOOK.with_borrow_mut(|this| *this = self.0.take())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current error hook.
|
||||
pub fn get_error_hook() -> Option<Arc<dyn ErrorHook>> {
|
||||
ERROR_HOOK.with_borrow(Clone::clone)
|
||||
}
|
||||
|
||||
/// Sets the current thread-local error hook, which will be invoked when [`throw`] is called.
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) {
|
||||
ERROR_HOOK.with_borrow_mut(|this| *this = Some(hook))
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) -> ResetErrorHookOnDrop {
|
||||
ResetErrorHookOnDrop(
|
||||
ERROR_HOOK.with_borrow_mut(|this| mem::replace(this, Some(hook))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Invokes the error hook set by [`set_error_hook`] with the given error.
|
||||
@@ -140,9 +156,10 @@ where
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Some(hook) = &this.hook {
|
||||
set_error_hook(Arc::clone(hook))
|
||||
}
|
||||
let _hook = this
|
||||
.hook
|
||||
.as_ref()
|
||||
.map(|hook| set_error_hook(Arc::clone(hook)));
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
edition = "2021"
|
||||
version = "0.1.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Spawn asynchronous tasks in an executor-independent way."
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
glib = { version = "0.19", optional = true }
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
futures = "0.3.30"
|
||||
glib = { version = "0.20.0", optional = true }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.42", optional = true }
|
||||
|
||||
[features]
|
||||
tracing = ["dep:tracing"]
|
||||
|
||||
@@ -6,31 +6,31 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
l0410 = { package = "leptos", version = "0.4.10", features = [
|
||||
"nightly",
|
||||
"ssr",
|
||||
"nightly",
|
||||
"ssr",
|
||||
] }
|
||||
leptos = { path = "../leptos", features = ["ssr", "nightly"] }
|
||||
leptos_reactive = { path = "../leptos_reactive", features = ["ssr", "nightly"] }
|
||||
tachydom = { git = "https://github.com/gbj/tachys", features = [
|
||||
"nightly",
|
||||
"leptos",
|
||||
"nightly",
|
||||
"leptos",
|
||||
] }
|
||||
tachy_maccy = { git = "https://github.com/gbj/tachys", features = ["nightly"] }
|
||||
sycamore = { version = "0.8", features = ["ssr"] }
|
||||
yew = { version = "0.20", features = ["ssr"] }
|
||||
tokio-test = "0.4"
|
||||
miniserde = "0.1"
|
||||
gloo = "0.8"
|
||||
uuid = { version = "1", features = ["serde", "v4", "wasm-bindgen"] }
|
||||
wasm-bindgen = "0.2"
|
||||
lazy_static = "1"
|
||||
log = "0.4"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
serde_json = "1"
|
||||
tera = "1"
|
||||
sycamore = { version = "0.8.0", features = ["ssr"] }
|
||||
yew = { version = "0.20.0", features = ["ssr"] }
|
||||
tokio-test = "0.4.0"
|
||||
miniserde = "0.1.0"
|
||||
gloo = "0.8.0"
|
||||
uuid = { version = "1.0", features = ["serde", "v4", "wasm-bindgen"] }
|
||||
wasm-bindgen = "0.2.0"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4.0"
|
||||
strum = "0.24.0"
|
||||
strum_macros = "0.24.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
tera = "1.0"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
version = "0.3.0"
|
||||
features = ["Window", "Document", "HtmlElement", "HtmlInputElement"]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[package]
|
||||
name = "const_str_slice_concat"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -8,5 +7,6 @@ readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for const concatenation of string slices."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -8,6 +7,7 @@ readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for working with enumerated types that contain one of 2..n other types."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2"
|
||||
pin-project-lite = "0.2.14"
|
||||
|
||||
@@ -7,19 +7,18 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
cfg-if = "1"
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
cfg-if = "1.0"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
wasm-bindgen = "0.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
|
||||
@@ -1,68 +1,9 @@
|
||||
<picture>
|
||||
<source srcset="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_Solid_White.svg" media="(prefers-color-scheme: dark)">
|
||||
<img src="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_RGB.svg" alt="Leptos Logo">
|
||||
</picture>
|
||||
# Action Form Error Handling Example
|
||||
|
||||
# Leptos Starter Template
|
||||
## Getting Started
|
||||
|
||||
This is a template for use with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## Creating your template repo
|
||||
## Quick Start
|
||||
|
||||
If you don't have `cargo-leptos` installed you can install it with
|
||||
|
||||
`cargo install cargo-leptos`
|
||||
|
||||
Then run
|
||||
|
||||
`cargo leptos new --git leptos-rs/start`
|
||||
|
||||
to generate a new project template (you will be prompted to enter a project name).
|
||||
|
||||
`cd {projectname}`
|
||||
|
||||
to go to your newly created project.
|
||||
|
||||
Of course, you should explore around the project structure, but the best place to start with your application code is in `src/app.rs`.
|
||||
|
||||
## Running your project
|
||||
|
||||
`cargo leptos watch`
|
||||
By default, you can access your local project at `http://localhost:3000`
|
||||
|
||||
## Installing Additional Tools
|
||||
|
||||
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
|
||||
|
||||
1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
|
||||
2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
|
||||
3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
|
||||
4. `npm install -g sass` - install `dart-sass` (should be optional in future)
|
||||
|
||||
## Executing a Server on a Remote Machine Without the Toolchain
|
||||
After running a `cargo leptos build --release` the minimum files needed are:
|
||||
|
||||
1. The server binary located in `target/server/release`
|
||||
2. The `site` directory and all files within located in `target/site`
|
||||
|
||||
Copy these files to your remote server. The directory structure should be:
|
||||
```text
|
||||
leptos_start
|
||||
site/
|
||||
```
|
||||
Set the following environment variables (updating for your project as needed):
|
||||
```sh
|
||||
export LEPTOS_OUTPUT_NAME="leptos_start"
|
||||
export LEPTOS_SITE_ROOT="site"
|
||||
export LEPTOS_SITE_PKG_DIR="pkg"
|
||||
export LEPTOS_SITE_ADDR="127.0.0.1:3000"
|
||||
export LEPTOS_RELOAD_PORT="3001"
|
||||
```
|
||||
Finally, run the server binary.
|
||||
|
||||
## Notes about CSR and Trunk:
|
||||
Although it is not recommended, you can also run your project without server integration using the feature `csr` and `trunk serve`:
|
||||
|
||||
`trunk serve --open --features csr`
|
||||
|
||||
This may be useful for integrating external tools which require a static site, e.g. `tauri`.
|
||||
Execute `cargo leptos watch` to run this example.
|
||||
|
||||
@@ -52,23 +52,10 @@ async fn main() -> std::io::Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "ssr", feature = "csr")))]
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn main() {
|
||||
// no client-side main function
|
||||
// unless we want this to work with e.g., Trunk for pure client-side testing
|
||||
// see lib.rs for hydration function instead
|
||||
// see optional feature `csr` instead
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "ssr"), feature = "csr"))]
|
||||
pub fn main() {
|
||||
// a client-side main function is required for using `trunk serve`
|
||||
// prefer using `cargo leptos serve` instead
|
||||
// to run: `trunk serve --open --features csr`
|
||||
use action_form_error_handling::app::*;
|
||||
use leptos::prelude::*;
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
mount_to_body(App);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
gloo-timers = { version = "0.3.0", features = ["futures"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
@@ -11,34 +11,34 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
broadcaster = "1"
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
lazy_static = "1"
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
broadcaster = "1.0"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
lazy_static = "1.5"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4"
|
||||
once_cell = "1.18"
|
||||
gloo-net = { git = "https://github.com/rustwasm/gloo" }
|
||||
wasm-bindgen = "0.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
simple_logger = "4.3"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
log = "0.4.22"
|
||||
once_cell = "1.19"
|
||||
gloo-net = { version = "0.6.0" }
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
simple_logger = "5.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:tracing",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_router/ssr",
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:tracing",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -113,10 +113,10 @@ pub fn Counter() -> impl IntoView {
|
||||
</p>
|
||||
<ErrorBoundary fallback=|errors| move || format!("Error: {:#?}", errors.get())>
|
||||
<div>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<button on:click=move |_| { clear.dispatch(()); }>"Clear"</button>
|
||||
<button on:click=move |_| { dec.dispatch(()); }>"-1"</button>
|
||||
<span>"Value: " <Suspense>{counter} "!"</Suspense></span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
<button on:click=move |_| { inc.dispatch(()); }>"+1"</button>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@@ -224,12 +224,12 @@ pub fn MultiuserCounter() -> impl IntoView {
|
||||
"This one uses server-sent events (SSE) to live-update when other users make changes."
|
||||
</p>
|
||||
<div>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<button on:click=move |_| { clear.dispatch(()); }>"Clear"</button>
|
||||
<button on:click=move |_| { dec.dispatch(()); }>"-1"</button>
|
||||
<span>
|
||||
"Multiplayer Value: " {move || multiplayer_value.get().unwrap_or_default()}
|
||||
</span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
<button on:click=move |_| { inc.dispatch(()); }>"+1"</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ leptos_router = { path = "../../router", features = [] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
@@ -14,10 +14,10 @@ console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.34"
|
||||
pretty_assertions = "1.3.0"
|
||||
rstest = "0.17.0"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
pretty_assertions = "1.4"
|
||||
rstest = "0.22.0"
|
||||
|
||||
[dev-dependencies.web-sys]
|
||||
features = ["HtmlElement", "XPathResult"]
|
||||
version = "0.3.61"
|
||||
version = "0.3.70"
|
||||
|
||||
@@ -4,10 +4,10 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = "0.3"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
@@ -5,12 +5,12 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
log = "0.4"
|
||||
console_log = "1"
|
||||
log = "0.4.22"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
web-sys = { version = "0.3", features = ["Clipboard", "Navigator"] }
|
||||
web-sys = { version = "0.3.70", features = ["Clipboard", "Navigator"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["NodeList"] }
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = { version = "0.3.70", features = ["NodeList"] }
|
||||
@@ -24,11 +24,7 @@ pub fn copy_to_clipboard(el: Element, content: &str) {
|
||||
evt.prevent_default();
|
||||
evt.stop_propagation();
|
||||
|
||||
let _ = window()
|
||||
.navigator()
|
||||
.clipboard()
|
||||
.expect("navigator.clipboard to be available")
|
||||
.write_text(&content);
|
||||
let _ = window().navigator().clipboard().write_text(&content);
|
||||
|
||||
el.set_inner_html(&format!("Copied \"{}\"", &content));
|
||||
});
|
||||
@@ -53,7 +49,6 @@ impl From<()> for Amount {
|
||||
}
|
||||
}
|
||||
|
||||
// .into() will automatically be called on the parameter
|
||||
pub fn add_dot(el: Element, amount: Amount) {
|
||||
use leptos::wasm_bindgen::JsCast;
|
||||
let el = el.unchecked_into::<web_sys::HtmlElement>();
|
||||
@@ -82,12 +77,17 @@ pub fn App() -> impl IntoView {
|
||||
let data = "Hello World!";
|
||||
|
||||
view! {
|
||||
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
|
||||
<a href="#" use:copy_to_clipboard=data>
|
||||
"Copy \""
|
||||
{data}
|
||||
"\" to clipboard"
|
||||
</a>
|
||||
// automatically applies the directive to every root element in `SomeComponent`
|
||||
<SomeComponent use:highlight />
|
||||
<SomeComponent use:highlight/>
|
||||
// no value will default to `().into()`
|
||||
<button use:add_dot>"Add a dot"</button>
|
||||
// `5.into()` automatically called
|
||||
<button use:add_dot=5>"Add 5 dots"</button>
|
||||
// can manually call `.into()` to convert to the correct type
|
||||
// (automatically calling `.into()` prevents using generics in directive functions)
|
||||
<button use:add_dot=5.into()>"Add 5 dots"</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
@@ -7,19 +7,19 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1" }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
|
||||
@@ -9,16 +9,16 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
|
||||
reqwasm = "0.5"
|
||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
log = "0.4"
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1"
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tracing-subscriber-wasm = "0.1"
|
||||
reqwasm = "0.5.0"
|
||||
gloo-timers = { version = "0.3.0", features = ["futures"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4.22"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
tracing-subscriber-wasm = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
|
||||
@@ -4,15 +4,15 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
leptos = { path = "../../leptos" }
|
||||
throw_error = { path = "../../any_error/" }
|
||||
any_spawner = { path = "../../any_spawner/" }
|
||||
|
||||
# these are used to build the integration
|
||||
gtk = { version = "0.9.0", package = "gtk4" }
|
||||
next_tuple = { path = "../../next_tuple/" }
|
||||
gtk = { version = "0.8.0", package = "gtk4", optional = true }
|
||||
paste = "1.0.14"
|
||||
paste = "1.0"
|
||||
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
[features]
|
||||
gtk = ["dep:gtk", "any_spawner/glib"]
|
||||
wasm = ["any_spawner/wasm-bindgen", "dep:console_error_panic_hook"]
|
||||
# we want to support using glib for the reactive runtime event loop
|
||||
any_spawner = { path = "../../any_spawner/", features = ["glib"] }
|
||||
# yes, we want effects to run: this is a "frontend," not a backend
|
||||
reactive_graph = { path = "../../reactive_graph", features = ["effects"] }
|
||||
|
||||
@@ -56,11 +56,12 @@ impl Mountable<LeptosGtk> for Element {
|
||||
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<LeptosGtk>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.as_ref()));
|
||||
true
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
if let Some(parent) = self.0.parent() {
|
||||
child.mount(&Element(parent), Some(self));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +80,8 @@ impl Mountable<LeptosGtk> for Text {
|
||||
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<LeptosGtk>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.as_ref()));
|
||||
true
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
self.0.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,16 +330,12 @@ where
|
||||
parent: &<LeptosGtk as Renderer>::Element,
|
||||
marker: Option<&<LeptosGtk as Renderer>::Node>,
|
||||
) {
|
||||
println!("mounting {}", std::any::type_name::<Widg>());
|
||||
self.children.mount(&self.widget, None);
|
||||
LeptosGtk::insert_node(parent, &self.widget, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<LeptosGtk>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.widget.as_ref()));
|
||||
true
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
self.widget.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
#[cfg(feature = "gtk")]
|
||||
use gtk::{
|
||||
glib::Value, prelude::*, Application, ApplicationWindow, Orientation,
|
||||
Widget,
|
||||
};
|
||||
#[cfg(feature = "wasm")]
|
||||
use leptos::tachys::{dom::body, html::element, html::event as ev};
|
||||
use leptos::{
|
||||
logging,
|
||||
prelude::*,
|
||||
reactive_graph::{effect::Effect, owner::Owner, signal::RwSignal},
|
||||
Executor, For, ForProps,
|
||||
};
|
||||
#[cfg(feature = "gtk")]
|
||||
use leptos_gtk::{Element, LGtkWidget, LeptosGtk};
|
||||
use any_spawner::Executor;
|
||||
use gtk::{prelude::*, Application, ApplicationWindow, Orientation};
|
||||
use leptos::prelude::*;
|
||||
use leptos_gtk::LeptosGtk;
|
||||
use std::{mem, thread, time::Duration};
|
||||
#[cfg(feature = "gtk")]
|
||||
mod leptos_gtk;
|
||||
|
||||
const APP_ID: &str = "dev.leptos.Counter";
|
||||
@@ -22,59 +10,39 @@ const APP_ID: &str = "dev.leptos.Counter";
|
||||
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
|
||||
fn main() {
|
||||
// use the glib event loop to power the reactive system
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
_ = Executor::init_glib();
|
||||
let app = Application::builder().application_id(APP_ID).build();
|
||||
_ = Executor::init_glib();
|
||||
let app = Application::builder().application_id(APP_ID).build();
|
||||
|
||||
app.connect_startup(|_| load_css());
|
||||
app.connect_startup(|_| load_css());
|
||||
|
||||
app.connect_activate(|app| {
|
||||
// Connect to "activate" signal of `app`
|
||||
let owner = Owner::new();
|
||||
let view = owner.with(ui);
|
||||
let (root, state) = leptos_gtk::root(view);
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("TachyGTK")
|
||||
.child(&root)
|
||||
.build();
|
||||
// Present window
|
||||
window.present();
|
||||
mem::forget((owner, state));
|
||||
});
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
console_error_panic_hook::set_once();
|
||||
_ = Executor::init_wasm_bindgen();
|
||||
app.connect_activate(|app| {
|
||||
// Connect to "activate" signal of `app`
|
||||
let owner = Owner::new();
|
||||
let view = owner.with(ui);
|
||||
let mut state = view.build();
|
||||
state.mount(&body().into(), None);
|
||||
let (root, state) = leptos_gtk::root(view);
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("TachyGTK")
|
||||
.child(&root)
|
||||
.build();
|
||||
// Present window
|
||||
window.present();
|
||||
mem::forget((owner, state));
|
||||
}
|
||||
});
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
#[cfg(feature = "gtk")]
|
||||
type Rndr = LeptosGtk;
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
type Rndr = Dom;
|
||||
|
||||
fn ui() -> impl Render<Rndr> {
|
||||
fn ui() -> impl Render<LeptosGtk> {
|
||||
let value = RwSignal::new(0);
|
||||
let rows = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
|
||||
Effect::new(move |_| {
|
||||
logging::log!("value = {}", value.get());
|
||||
println!("value = {}", value.get());
|
||||
});
|
||||
|
||||
// just an example of multithreaded reactivity
|
||||
#[cfg(feature = "gtk")]
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
value.update(|n| *n += 1);
|
||||
@@ -82,7 +50,10 @@ fn ui() -> impl Render<Rndr> {
|
||||
|
||||
vstack((
|
||||
hstack((
|
||||
button("-1", move || value.update(|n| *n -= 1)),
|
||||
button("-1", move || {
|
||||
println!("clicked -1");
|
||||
value.update(|n| *n -= 1);
|
||||
}),
|
||||
move || value.get().to_string(),
|
||||
button("+1", move || value.update(|n| *n += 1)),
|
||||
)),
|
||||
@@ -91,75 +62,36 @@ fn ui() -> impl Render<Rndr> {
|
||||
items.swap(1, 3);
|
||||
})
|
||||
}),
|
||||
hstack(For(ForProps::builder()
|
||||
.each(move || rows.get())
|
||||
.key(|k| *k)
|
||||
.children(|v| v)
|
||||
.build())),
|
||||
hstack(rows),
|
||||
))
|
||||
}
|
||||
|
||||
fn button(
|
||||
label: impl Render<Rndr>,
|
||||
label: impl Render<LeptosGtk>,
|
||||
callback: impl Fn() + Send + Sync + 'static,
|
||||
) -> impl Render<Rndr> {
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
leptos_gtk::button()
|
||||
.child(label)
|
||||
.connect("clicked", move |_| {
|
||||
callback();
|
||||
None
|
||||
})
|
||||
}
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
element::button()
|
||||
.on(ev::click, move |_| callback())
|
||||
.child(label)
|
||||
}
|
||||
) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::button()
|
||||
.child(label)
|
||||
.connect("clicked", move |_| {
|
||||
callback();
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn vstack(children: impl Render<Rndr>) -> impl Render<Rndr> {
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Vertical)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
element::div()
|
||||
.style(("display", "flex"))
|
||||
.style(("flex-direction", "column"))
|
||||
.style(("align-items", "center"))
|
||||
.style(("justify-content", "center"))
|
||||
.style(("margin", "1rem"))
|
||||
.child(children)
|
||||
}
|
||||
fn vstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Vertical)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
|
||||
fn hstack(children: impl Render<Rndr>) -> impl Render<Rndr> {
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
element::div()
|
||||
.style(("display", "flex"))
|
||||
.style(("align-items", "center"))
|
||||
.style(("justify-content", "center"))
|
||||
.style(("margin", "1rem"))
|
||||
.child(children)
|
||||
}
|
||||
fn hstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
|
||||
#[cfg(feature = "gtk")]
|
||||
fn load_css() {
|
||||
use gtk::{gdk::Display, CssProvider};
|
||||
|
||||
|
||||
@@ -13,31 +13,27 @@ panic = "abort"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1"
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:leptos_actix",
|
||||
"leptos/ssr",
|
||||
]
|
||||
ssr = ["dep:actix-files", "dep:actix-web", "dep:leptos_actix", "leptos/ssr"]
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
|
||||
@@ -11,22 +11,22 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
gloo-net = { version = "0.4", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0", optional = true }
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2"
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -11,34 +11,35 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"experimental-islands",
|
||||
] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos", features = ["experimental-islands"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router"}
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
gloo-net = { version = "0.4", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
axum = { version = "0.7", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = [
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
"compression-gzip",
|
||||
"compression-br",
|
||||
], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0", optional = true }
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2"
|
||||
lazy_static = "1.4.0"
|
||||
rust-embed = { version = "8", features = ["axum", "mime_guess", "tokio"], optional = true }
|
||||
mime_guess = { version = "2.0.4", optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
lazy_static = "1.5"
|
||||
rust-embed = { version = "8.5", features = [
|
||||
"axum",
|
||||
"mime_guess",
|
||||
"tokio",
|
||||
], optional = true }
|
||||
mime_guess = { version = "2.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
|
||||
@@ -11,30 +11,30 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
log = "0.4"
|
||||
log = "0.4.22"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
leptos_server = { path = "../../leptos_server", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
gloo-net = { version = "0.5", features = ["http"] }
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
axum = { version = "0.7", default-features = false, optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
http = { version = "1.0", optional = true }
|
||||
web-sys = { version = "0.3", features = [
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", default-features = false, optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = [
|
||||
"AbortController",
|
||||
"AbortSignal",
|
||||
"Request",
|
||||
"Response",
|
||||
] }
|
||||
getrandom = { version = "0.2.7", features = ["js"] }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = { version = "0.4.37", features = [
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-futures = { version = "0.4.42", features = [
|
||||
"futures-core-03-stream",
|
||||
], optional = true }
|
||||
axum-js-fetch = { git = "https://github.com/seanaye/axum-js-fetch", optional = true }
|
||||
|
||||
@@ -7,32 +7,32 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
|
||||
@@ -7,33 +7,35 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = ["islands-router"], optional = true }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
leptos_axum = { path = "../../integrations/axum", features = [
|
||||
"islands-router",
|
||||
], optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
|
||||
@@ -8,13 +8,13 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `event-delegation` features
|
||||
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `delegation` features
|
||||
# used in rand, but we need to enable js feature
|
||||
getrandom = { version = "0.2.7", features = ["js"] }
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -9,7 +9,7 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
web-sys = "0.3"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
@@ -5,12 +5,12 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
log = "0.4"
|
||||
console_log = "1"
|
||||
log = "0.4.22"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = "0.3"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = "0.3.70"
|
||||
@@ -10,16 +10,16 @@ codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
console_log = "1"
|
||||
console_log = "1.0"
|
||||
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
|
||||
leptos_router = { path = "../../router" } #, features = ["tracing"] }
|
||||
leptos_router = { path = "../../router" } #, features = ["tracing"] }
|
||||
leptos_router_macro = { path = "../../router_macro" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
futures = "0.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
futures = "0.3.30"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
tracing-subscriber = "0.3.18"
|
||||
tracing-subscriber-wasm = "0.1.0"
|
||||
tracing = "0.1.40"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
|
||||
@@ -72,6 +72,7 @@ pub fn ContactRoutes() -> impl MatchNestedRoutes<Dom> + Clone {
|
||||
<Route path=path!("/:id") view=Contact/>
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -7,44 +7,52 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart"] }
|
||||
log = "0.4"
|
||||
simple_logger = "4.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs", "tracing", "trace"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
server_fn = { path = "../../server_fn", features = [
|
||||
"serde-lite",
|
||||
"rkyv",
|
||||
"multipart",
|
||||
] }
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
"tracing",
|
||||
"trace",
|
||||
], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde_toml = "0.0.1"
|
||||
toml = "0.8.8"
|
||||
web-sys = { version = "0.3.67", features = ["FileList", "File"] }
|
||||
strum = { version = "0.25.0", features = ["strum_macros", "derive"] }
|
||||
notify = { version = "6.1.1", optional = true }
|
||||
pin-project-lite = "0.2.13"
|
||||
dashmap = { version = "5.5.3", optional = true }
|
||||
once_cell = { version = "1.19.0", optional = true }
|
||||
async-broadcast = { version = "0.6.0", optional = true }
|
||||
toml = "0.8.19"
|
||||
web-sys = { version = "0.3.70", features = ["FileList", "File"] }
|
||||
strum = { version = "0.26.3", features = ["strum_macros", "derive"] }
|
||||
notify = { version = "6.1", optional = true }
|
||||
pin-project-lite = "0.2.14"
|
||||
dashmap = { version = "6.0", optional = true }
|
||||
once_cell = { version = "1.19", optional = true }
|
||||
async-broadcast = { version = "0.7.1", optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:notify",
|
||||
"dep:dashmap",
|
||||
"dep:once_cell",
|
||||
"dep:async-broadcast",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:notify",
|
||||
"dep:dashmap",
|
||||
"dep:once_cell",
|
||||
"dep:async-broadcast",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -9,6 +9,6 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
@@ -7,20 +7,20 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
lazy_static = "1"
|
||||
leptos = { path = "../../leptos"}
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
lazy_static = "1.5"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
wasm-bindgen = "0.2"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", features = ["time"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
|
||||
@@ -7,37 +7,39 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
lazy_static = "1"
|
||||
leptos = { path = "../../leptos", features = ["hydration" ] } #"nightly", "hydration"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
lazy_static = "1.5"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"hydration",
|
||||
] } #"nightly", "hydration"] }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"dep:leptos_axum",
|
||||
"leptos_router/ssr",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"dep:leptos_axum",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone() />
|
||||
<AutoReload options=options.clone()/>
|
||||
<HydrationScripts options/>
|
||||
<MetaTags/>
|
||||
</head>
|
||||
@@ -64,15 +64,24 @@ pub fn App() -> impl IntoView {
|
||||
<a href="/admin">"Admin"</a>
|
||||
<Transition>
|
||||
<ActionForm action=toggle_admin>
|
||||
<input type="hidden" name="is_admin"
|
||||
value=move || (!is_admin.get().and_then(|n| n.ok()).unwrap_or_default()).to_string()
|
||||
<input
|
||||
type="hidden"
|
||||
name="is_admin"
|
||||
value=move || {
|
||||
(!is_admin.get().and_then(|n| n.ok()).unwrap_or_default())
|
||||
.to_string()
|
||||
}
|
||||
/>
|
||||
|
||||
<button>
|
||||
{move || if is_admin.get().and_then(Result::ok).unwrap_or_default() {
|
||||
"Log Out"
|
||||
} else {
|
||||
"Log In"
|
||||
{move || {
|
||||
if is_admin.get().and_then(Result::ok).unwrap_or_default() {
|
||||
"Log Out"
|
||||
} else {
|
||||
"Log In"
|
||||
}
|
||||
}}
|
||||
|
||||
</button>
|
||||
</ActionForm>
|
||||
</Transition>
|
||||
@@ -139,9 +148,15 @@ fn HomePage() -> impl IntoView {
|
||||
<li>
|
||||
<a href=format!("/post/{}", post.id)>{post.title.clone()}</a>
|
||||
"|"
|
||||
<a href=format!("/post_in_order/{}", post.id)>{post.title.clone()} "(in order)"</a>
|
||||
<a href=format!(
|
||||
"/post_in_order/{}",
|
||||
post.id,
|
||||
)>{post.title.clone()} "(in order)"</a>
|
||||
"|"
|
||||
<a href=format!("/post_partially_blocked/{}", post.id)>{post.title} "(partially blocked)"</a>
|
||||
<a href=format!(
|
||||
"/post_partially_blocked/{}",
|
||||
post.id,
|
||||
)>{post.title} "(partially blocked)"</a>
|
||||
</li>
|
||||
</For>
|
||||
</ul>
|
||||
@@ -204,12 +219,11 @@ fn Post() -> impl IntoView {
|
||||
Ok(comments) => Ok(view! {
|
||||
<h1>"Comments"</h1>
|
||||
<ul>
|
||||
{comments.into_iter()
|
||||
.map(|comment| view! {
|
||||
<li>{comment}</li>
|
||||
})
|
||||
.collect_view()
|
||||
}
|
||||
{comments
|
||||
.into_iter()
|
||||
.map(|comment| view! { <li>{comment}</li> })
|
||||
.collect_view()}
|
||||
|
||||
</ul>
|
||||
}),
|
||||
_ => Err(PostError::ServerError),
|
||||
@@ -237,17 +251,13 @@ fn Post() -> impl IntoView {
|
||||
}
|
||||
}>{post_view}</ErrorBoundary>
|
||||
</Suspense>
|
||||
<Suspense fallback=move || view! { <p>"Loading comments..."</p> }>
|
||||
{comments_view}
|
||||
</Suspense>
|
||||
<Suspense fallback=move || view! { <p>"Loading comments..."</p> }>{comments_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Admin() -> impl IntoView {
|
||||
view! {
|
||||
<p>"You can only see this page if you're logged in."</p>
|
||||
}
|
||||
view! { <p>"You can only see this page if you're logged in."</p> }
|
||||
}
|
||||
|
||||
// Dummy API
|
||||
|
||||
@@ -15,6 +15,6 @@ reactive_stores_macro = { path = "../../reactive_stores_macro" }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
@@ -7,26 +7,26 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4"
|
||||
wasm-bindgen = "0.2"
|
||||
serde = "1.0.159"
|
||||
tokio = { version = "1.29", features = ["time", "rt"], optional = true }
|
||||
log = "0.4.22"
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = "1.0"
|
||||
tokio = { version = "1.39", features = ["time", "rt"], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:leptos_actix",
|
||||
"leptos/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tokio",
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:leptos_actix",
|
||||
"leptos/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tokio",
|
||||
]
|
||||
|
||||
[package.metadata.leptos]
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.72"
|
||||
async-trait = "0.1.72"
|
||||
cucumber = "0.19.1"
|
||||
fantoccini = "0.19.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_json = "1.0.104"
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.4.0"
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
|
||||
@@ -8,8 +8,8 @@ This example demonstrates e2e testing with Rust using executable requirements.
|
||||
|---|---|---|
|
||||
| [Cucumber](https://github.com/cucumber-rs/cucumber/tree/main) | Test Runner | Run [Gherkin](https://cucumber.io/docs/gherkin/reference/) specifications as Rust tests |
|
||||
| [Fantoccini](https://github.com/jonhoo/fantoccini/tree/main) | Browser Client | Interact with web pages through WebDriver |
|
||||
| [Cargo Leptos ](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
|
||||
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome
|
||||
| [Cargo Leptos](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
|
||||
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome |
|
||||
|
||||
## Testing Organization
|
||||
|
||||
|
||||
@@ -13,19 +13,15 @@ leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
|
||||
# dependencies for browser (enable when hydrate set)
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
wasm-bindgen = { version = "0.2.93", optional = true }
|
||||
|
||||
# dependencies for server (enable when ssr set)
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", features = ["macros"], optional = true }
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", features = ["macros"], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"dep:wasm-bindgen",
|
||||
"dep:console_error_panic_hook",
|
||||
]
|
||||
hydrate = ["leptos/hydrate", "dep:wasm-bindgen", "dep:console_error_panic_hook"]
|
||||
ssr = [
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
|
||||
@@ -7,32 +7,35 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"], optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
], optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
thiserror = "1.0"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
http = "1.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
http = "1.1"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -7,5 +7,5 @@ edition = "2021"
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
gloo-net = { version = "0.5", features = ["http"] }
|
||||
console_error_panic_hook = { version = "0.1" }
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
|
||||
@@ -8,15 +8,15 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
version = "0.3.70"
|
||||
features = ["Window"]
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
|
||||
@@ -7,36 +7,36 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6.2", optional = true }
|
||||
actix-web = { version = "4.2.1", optional = true, features = ["macros"] }
|
||||
anyhow = "1.0.68"
|
||||
broadcaster = "1.0.0"
|
||||
console_log = "1.0.0"
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
anyhow = "1.0"
|
||||
broadcaster = "1.0"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
futures = "0.3.25"
|
||||
leptos = { path = "../../leptos"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
futures = "0.3.30"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
log = "0.4.17"
|
||||
simple_logger = "4.0.0"
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
gloo = { git = "https://github.com/rustwasm/gloo" }
|
||||
sqlx = { version = "0.6.2", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
tokio = { version = "1", features = ["rt", "time"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
tokio = { version = "1.39", features = ["rt", "time"], optional = true }
|
||||
server_fn = { path = "../../server_fn", features = ["cbor"] }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"dep:tokio",
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"dep:tokio",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.72"
|
||||
async-trait = "0.1.72"
|
||||
cucumber = "0.19.1"
|
||||
fantoccini = "0.19.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_json = "1.0.104"
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.4.0"
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
|
||||
@@ -8,35 +8,35 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = ["tracing"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4"
|
||||
simple_logger = "4.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
sqlx = { version = "0.7", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
], optional = true }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.72"
|
||||
async-trait = "0.1.72"
|
||||
cucumber = "0.19.1"
|
||||
fantoccini = "0.19.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_json = "1.0.104"
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.4.0"
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
|
||||
@@ -7,39 +7,39 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta"}
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
leptos_integration_utils = { path = "../../integrations/utils", optional = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0" }
|
||||
sqlx = { version = "0.7", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1" }
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
], optional = true }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
csr = ["leptos/csr"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:leptos_integration_utils",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:leptos_integration_utils",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.72"
|
||||
async-trait = "0.1.72"
|
||||
cucumber = "0.19.1"
|
||||
fantoccini = "0.19.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_json = "1.0.104"
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.4.0"
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
|
||||
@@ -8,19 +8,15 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
uuid = { version = "1", features = ["v4", "js", "serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
web-sys = { version = "0.3.60", features = ["Storage"] }
|
||||
uuid = { version = "1.10", features = ["v4", "js", "serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
web-sys = { version = "0.3.70", features = ["Storage"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
csr = ["leptos/csr"]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
[package]
|
||||
name = "hydration_context"
|
||||
edition = "2021"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta2"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for sharing data between web servers and client-side web applications."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
throw_error = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
futures = "0.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
once_cell = "1.19.0"
|
||||
futures = "0.3.30"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen = { version = "0.2.93", optional = true }
|
||||
js-sys = { version = "0.3.69", optional = true }
|
||||
once_cell = "1.19"
|
||||
pin-project-lite = "0.2.14"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -12,42 +12,48 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(thread_local)]
|
||||
static __RESOLVED_RESOURCES: Array;
|
||||
|
||||
#[wasm_bindgen(thread_local)]
|
||||
static __SERIALIZED_ERRORS: Array;
|
||||
|
||||
#[wasm_bindgen(thread_local)]
|
||||
static __INCOMPLETE_CHUNKS: Array;
|
||||
}
|
||||
|
||||
fn serialized_errors() -> Vec<(SerializedDataId, ErrorId, Error)> {
|
||||
__SERIALIZED_ERRORS
|
||||
.iter()
|
||||
.flat_map(|value| {
|
||||
value.dyn_ref::<Array>().map(|value| {
|
||||
let error_boundary_id = value.get(0).as_f64().unwrap() as usize;
|
||||
let error_id = value.get(1).as_f64().unwrap() as usize;
|
||||
let value = value
|
||||
.get(2)
|
||||
.as_string()
|
||||
.expect("Expected a [number, string] tuple");
|
||||
(
|
||||
SerializedDataId(error_boundary_id),
|
||||
ErrorId::from(error_id),
|
||||
Error::from(SerializedError(value)),
|
||||
)
|
||||
__SERIALIZED_ERRORS.with(|s| {
|
||||
s.iter()
|
||||
.flat_map(|value| {
|
||||
value.dyn_ref::<Array>().map(|value| {
|
||||
let error_boundary_id =
|
||||
value.get(0).as_f64().unwrap() as usize;
|
||||
let error_id = value.get(1).as_f64().unwrap() as usize;
|
||||
let value = value
|
||||
.get(2)
|
||||
.as_string()
|
||||
.expect("Expected a [number, string] tuple");
|
||||
(
|
||||
SerializedDataId(error_boundary_id),
|
||||
ErrorId::from(error_id),
|
||||
Error::from(SerializedError(value)),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn incomplete_chunks() -> Vec<SerializedDataId> {
|
||||
__INCOMPLETE_CHUNKS
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let id = value.as_f64().unwrap() as usize;
|
||||
SerializedDataId(id)
|
||||
})
|
||||
.collect()
|
||||
__INCOMPLETE_CHUNKS.with(|i| {
|
||||
i.iter()
|
||||
.map(|value| {
|
||||
let id = value.as_f64().unwrap() as usize;
|
||||
SerializedDataId(id)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// An error that has been serialized across the network boundary.
|
||||
@@ -118,7 +124,7 @@ impl SharedContext for HydrateSharedContext {
|
||||
fn write_async(&self, _id: SerializedDataId, _fut: PinnedFuture<String>) {}
|
||||
|
||||
fn read_data(&self, id: &SerializedDataId) -> Option<String> {
|
||||
__RESOLVED_RESOURCES.get(id.0 as u32).as_string()
|
||||
__RESOLVED_RESOURCES.with(|r| r.get(id.0 as u32).as_string())
|
||||
}
|
||||
|
||||
fn await_data(&self, _id: &SerializedDataId) -> Option<String> {
|
||||
|
||||
@@ -44,6 +44,12 @@ pub type PinnedStream<T> = Pin<Box<dyn Stream<Item = T> + Send + Sync>>;
|
||||
/// from the server to the client.
|
||||
pub struct SerializedDataId(usize);
|
||||
|
||||
impl From<SerializedDataId> for ErrorId {
|
||||
fn from(value: SerializedDataId) -> Self {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information that will be shared between the server and the client.
|
||||
pub trait SharedContext: Debug {
|
||||
/// Whether the application is running in the browser.
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
[package]
|
||||
name = "leptos_actix"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Actix integrations for the Leptos web framework."
|
||||
version = { workspace = true }
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-http = "3"
|
||||
actix-web = "4"
|
||||
futures = "0.3"
|
||||
actix-http = "3.8"
|
||||
actix-web = "4.8"
|
||||
futures = "0.3.30"
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
@@ -20,10 +20,10 @@ leptos_macro = { workspace = true, features = ["actix"] }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
server_fn = { workspace = true, features = ["actix"] }
|
||||
serde_json = "1"
|
||||
parking_lot = "0.12.1"
|
||||
tracing = "0.1.37"
|
||||
tokio = { version = "1", features = ["rt", "fs"] }
|
||||
serde_json = "1.0"
|
||||
parking_lot = "0.12.3"
|
||||
tracing = "0.1.40"
|
||||
tokio = { version = "1.39", features = ["rt", "fs"] }
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -16,6 +16,7 @@ use actix_web::{
|
||||
};
|
||||
use futures::{stream::once, Stream, StreamExt};
|
||||
use http::StatusCode;
|
||||
use hydration_context::SsrSharedContext;
|
||||
use leptos::{
|
||||
context::{provide_context, use_context},
|
||||
reactive_graph::{computed::ScopedFuture, owner::Owner},
|
||||
@@ -181,6 +182,20 @@ impl ExtendResponse for ActixResponse {
|
||||
|
||||
/// Provides an easy way to redirect the user from within a server function.
|
||||
///
|
||||
/// Calling `redirect` in a server function will redirect the browser in three
|
||||
/// situations:
|
||||
/// 1. A server function that is calling in a [blocking
|
||||
/// resource](leptos::server::Resource::new_blocking).
|
||||
/// 2. A server function that is called from WASM running in the client (e.g., a dispatched action
|
||||
/// or a spawned `Future`).
|
||||
/// 3. A `<form>` submitted to the server function endpoint using default browser APIs (often due
|
||||
/// to using [`ActionForm`](leptos::form::ActionForm) without JS/WASM present.)
|
||||
///
|
||||
/// Using it with a non-blocking [`Resource`](leptos::server::Resource) will not work if you are using streaming rendering,
|
||||
/// as the response's headers will already have been sent by the time the server function calls `redirect()`.
|
||||
///
|
||||
/// ### Implementation
|
||||
///
|
||||
/// This sets the `Location` header to the URL given.
|
||||
///
|
||||
/// If the route or server function in which this is called is being accessed
|
||||
@@ -301,8 +316,9 @@ pub fn handle_server_fns_with_context(
|
||||
let additional_context = additional_context.clone();
|
||||
|
||||
let path = req.path();
|
||||
let method = req.method();
|
||||
if let Some(mut service) =
|
||||
server_fn::actix::get_server_fn_service(path)
|
||||
server_fn::actix::get_server_fn_service(path, method)
|
||||
{
|
||||
let owner = Owner::new();
|
||||
owner
|
||||
@@ -384,7 +400,7 @@ pub fn handle_server_fns_with_context(
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_router::Method;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
@@ -406,11 +422,7 @@ pub fn handle_server_fns_with_context(
|
||||
/// // the actual routing will be handled by `leptos_router`
|
||||
/// .route(
|
||||
/// "/{tail:.*}",
|
||||
/// leptos_actix::render_app_to_stream(
|
||||
/// leptos_options.to_owned(),
|
||||
/// || view! { <MyApp/> },
|
||||
/// Method::Get,
|
||||
/// ),
|
||||
/// leptos_actix::render_app_to_stream(MyApp, Method::Get),
|
||||
/// )
|
||||
/// })
|
||||
/// .bind(&addr)?
|
||||
@@ -452,7 +464,7 @@ where
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_router::Method;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
@@ -475,8 +487,7 @@ where
|
||||
/// .route(
|
||||
/// "/{tail:.*}",
|
||||
/// leptos_actix::render_app_to_stream_in_order(
|
||||
/// leptos_options.to_owned(),
|
||||
/// || view! { <MyApp/> },
|
||||
/// MyApp,
|
||||
/// Method::Get,
|
||||
/// ),
|
||||
/// )
|
||||
@@ -518,7 +529,7 @@ where
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_router::Method;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
@@ -540,11 +551,7 @@ where
|
||||
/// // the actual routing will be handled by `leptos_router`
|
||||
/// .route(
|
||||
/// "/{tail:.*}",
|
||||
/// leptos_actix::render_app_async(
|
||||
/// leptos_options.to_owned(),
|
||||
/// || view! { <MyApp/> },
|
||||
/// Method::Get,
|
||||
/// ),
|
||||
/// leptos_actix::render_app_async(MyApp, Method::Get),
|
||||
/// )
|
||||
/// })
|
||||
/// .bind(&addr)?
|
||||
@@ -944,7 +951,7 @@ where
|
||||
{
|
||||
let _ = any_spawner::Executor::init_tokio();
|
||||
|
||||
let owner = Owner::new_root(None);
|
||||
let owner = Owner::new_root(Some(Arc::new(SsrSharedContext::new())));
|
||||
let (mock_meta, _) = ServerMetaContext::new();
|
||||
let routes = owner
|
||||
.with(|| {
|
||||
@@ -1380,18 +1387,21 @@ impl LeptosRoutes for &mut ServiceConfig {
|
||||
///
|
||||
/// Any error that occurs during extraction is converted to a [`ServerFnError`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // MyQuery is some type that implements `Deserialize + Serialize`
|
||||
/// ```rust
|
||||
/// use leptos::prelude::*;
|
||||
///
|
||||
/// #[server]
|
||||
/// pub async fn query_extract() -> Result<MyQuery, ServerFnError> {
|
||||
/// use actix_web::web::Query;
|
||||
/// pub async fn extract_connection_info() -> Result<String, ServerFnError> {
|
||||
/// use actix_web::dev::ConnectionInfo;
|
||||
/// use leptos_actix::*;
|
||||
///
|
||||
/// let Query(data) = extract().await?;
|
||||
/// // this can be any type you can use an Actix extractor with, as long as
|
||||
/// // it works on the head, not the body of the request
|
||||
/// let info: ConnectionInfo = extract().await?;
|
||||
///
|
||||
/// // do something with the data
|
||||
///
|
||||
/// Ok(data)
|
||||
/// Ok(format!("{info:?}"))
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract<T>() -> Result<T, ServerFnError>
|
||||
@@ -1399,7 +1409,7 @@ where
|
||||
T: actix_web::FromRequest,
|
||||
<T as FromRequest>::Error: Display,
|
||||
{
|
||||
let req = use_context::<HttpRequest>().ok_or_else(|| {
|
||||
let req = use_context::<Request>().ok_or_else(|| {
|
||||
ServerFnError::new("HttpRequest should have been provided via context")
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1,148 +1,153 @@
|
||||
use leptos::*;
|
||||
use leptos_actix::generate_route_list;
|
||||
use leptos_router::{Route, Router, Routes, TrailingSlash};
|
||||
// TODO these tests relate to trailing-slash logic, which is still TBD for 0.7
|
||||
|
||||
#[component]
|
||||
fn DefaultApp() -> impl IntoView {
|
||||
let view = || view! { "" };
|
||||
view! {
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/foo" view/>
|
||||
<Route path="/bar/" view/>
|
||||
<Route path="/baz/:id" view/>
|
||||
<Route path="/baz/:name/" view/>
|
||||
<Route path="/baz/*any" view/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_app() {
|
||||
let routes = generate_route_list(DefaultApp);
|
||||
|
||||
// We still have access to the original (albeit normalized) Leptos paths:
|
||||
assert_same(
|
||||
&routes,
|
||||
|r| r.leptos_path(),
|
||||
&["/bar", "/baz/*any", "/baz/:id", "/baz/:name", "/foo"],
|
||||
);
|
||||
|
||||
// ... But leptos-actix has also reformatted "paths" to work for Actix.
|
||||
assert_same(
|
||||
&routes,
|
||||
|r| r.path(),
|
||||
&["/bar", "/baz/{id}", "/baz/{name}", "/baz/{tail:.*}", "/foo"],
|
||||
);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ExactApp() -> impl IntoView {
|
||||
let view = || view! { "" };
|
||||
let trailing_slash = TrailingSlash::Exact;
|
||||
view! {
|
||||
<Router trailing_slash>
|
||||
<Routes>
|
||||
<Route path="/foo" view/>
|
||||
<Route path="/bar/" view/>
|
||||
<Route path="/baz/:id" view/>
|
||||
<Route path="/baz/:name/" view/>
|
||||
<Route path="/baz/*any" view/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exact_app() {
|
||||
let routes = generate_route_list(ExactApp);
|
||||
|
||||
// In Exact mode, the Leptos paths no longer have their trailing slashes stripped:
|
||||
assert_same(
|
||||
&routes,
|
||||
|r| r.leptos_path(),
|
||||
&["/bar/", "/baz/*any", "/baz/:id", "/baz/:name/", "/foo"],
|
||||
);
|
||||
|
||||
// Actix paths also have trailing slashes as a result:
|
||||
assert_same(
|
||||
&routes,
|
||||
|r| r.path(),
|
||||
&[
|
||||
"/bar/",
|
||||
"/baz/{id}",
|
||||
"/baz/{name}/",
|
||||
"/baz/{tail:.*}",
|
||||
"/foo",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn RedirectApp() -> impl IntoView {
|
||||
let view = || view! { "" };
|
||||
let trailing_slash = TrailingSlash::Redirect;
|
||||
view! {
|
||||
<Router trailing_slash>
|
||||
<Routes>
|
||||
<Route path="/foo" view/>
|
||||
<Route path="/bar/" view/>
|
||||
<Route path="/baz/:id" view/>
|
||||
<Route path="/baz/:name/" view/>
|
||||
<Route path="/baz/*any" view/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_redirect_app() {
|
||||
let routes = generate_route_list(RedirectApp);
|
||||
|
||||
assert_same(
|
||||
&routes,
|
||||
|r| r.leptos_path(),
|
||||
&[
|
||||
"/bar",
|
||||
"/bar/",
|
||||
"/baz/*any",
|
||||
"/baz/:id",
|
||||
"/baz/:id/",
|
||||
"/baz/:name",
|
||||
"/baz/:name/",
|
||||
"/foo",
|
||||
"/foo/",
|
||||
],
|
||||
);
|
||||
|
||||
// ... But leptos-actix has also reformatted "paths" to work for Actix.
|
||||
assert_same(
|
||||
&routes,
|
||||
|r| r.path(),
|
||||
&[
|
||||
"/bar",
|
||||
"/bar/",
|
||||
"/baz/{id}",
|
||||
"/baz/{id}/",
|
||||
"/baz/{name}",
|
||||
"/baz/{name}/",
|
||||
"/baz/{tail:.*}",
|
||||
"/foo",
|
||||
"/foo/",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_same<'t, T, F, U>(
|
||||
input: &'t Vec<T>,
|
||||
mapper: F,
|
||||
expected_sorted_values: &[U],
|
||||
) where
|
||||
F: Fn(&'t T) -> U + 't,
|
||||
U: Ord + std::fmt::Debug,
|
||||
{
|
||||
let mut values: Vec<U> = input.iter().map(mapper).collect();
|
||||
values.sort();
|
||||
assert_eq!(values, expected_sorted_values);
|
||||
}
|
||||
// use leptos::*;
|
||||
// use leptos_actix::generate_route_list;
|
||||
// use leptos_router::{
|
||||
// components::{Route, Router, Routes},
|
||||
// path,
|
||||
// };
|
||||
//
|
||||
// #[component]
|
||||
// fn DefaultApp() -> impl IntoView {
|
||||
// let view = || view! { "" };
|
||||
// view! {
|
||||
// <Router>
|
||||
// <Routes>
|
||||
// <Route path=path!("/foo") view/>
|
||||
// <Route path=path!("/bar/") view/>
|
||||
// <Route path=path!("/baz/:id") view/>
|
||||
// <Route path=path!("/baz/:name/") view/>
|
||||
// <Route path=path!("/baz/*any") view/>
|
||||
// </Routes>
|
||||
// </Router>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_default_app() {
|
||||
// let routes = generate_route_list(DefaultApp);
|
||||
//
|
||||
// // We still have access to the original (albeit normalized) Leptos paths:
|
||||
// assert_same(
|
||||
// &routes,
|
||||
// |r| r.leptos_path(),
|
||||
// &["/bar", "/baz/*any", "/baz/:id", "/baz/:name", "/foo"],
|
||||
// );
|
||||
//
|
||||
// // ... But leptos-actix has also reformatted "paths" to work for Actix.
|
||||
// assert_same(
|
||||
// &routes,
|
||||
// |r| r.path(),
|
||||
// &["/bar", "/baz/{id}", "/baz/{name}", "/baz/{tail:.*}", "/foo"],
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[component]
|
||||
// fn ExactApp() -> impl IntoView {
|
||||
// let view = || view! { "" };
|
||||
// //let trailing_slash = TrailingSlash::Exact;
|
||||
// view! {
|
||||
// <Router>
|
||||
// <Routes>
|
||||
// <Route path=path!("/foo") view/>
|
||||
// <Route path=path!("/bar/") view/>
|
||||
// <Route path=path!("/baz/:id") view/>
|
||||
// <Route path=path!("/baz/:name/") view/>
|
||||
// <Route path=path!("/baz/*any") view/>
|
||||
// </Routes>
|
||||
// </Router>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_exact_app() {
|
||||
// let routes = generate_route_list(ExactApp);
|
||||
//
|
||||
// // In Exact mode, the Leptos paths no longer have their trailing slashes stripped:
|
||||
// assert_same(
|
||||
// &routes,
|
||||
// |r| r.leptos_path(),
|
||||
// &["/bar/", "/baz/*any", "/baz/:id", "/baz/:name/", "/foo"],
|
||||
// );
|
||||
//
|
||||
// // Actix paths also have trailing slashes as a result:
|
||||
// assert_same(
|
||||
// &routes,
|
||||
// |r| r.path(),
|
||||
// &[
|
||||
// "/bar/",
|
||||
// "/baz/{id}",
|
||||
// "/baz/{name}/",
|
||||
// "/baz/{tail:.*}",
|
||||
// "/foo",
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[component]
|
||||
// fn RedirectApp() -> impl IntoView {
|
||||
// let view = || view! { "" };
|
||||
// //let trailing_slash = TrailingSlash::Redirect;
|
||||
// view! {
|
||||
// <Router>
|
||||
// <Routes>
|
||||
// <Route path=path!("/foo") view/>
|
||||
// <Route path=path!("/bar/") view/>
|
||||
// <Route path=path!("/baz/:id") view/>
|
||||
// <Route path=path!("/baz/:name/") view/>
|
||||
// <Route path=path!("/baz/*any") view/>
|
||||
// </Routes>
|
||||
// </Router>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_redirect_app() {
|
||||
// let routes = generate_route_list(RedirectApp);
|
||||
//
|
||||
// assert_same(
|
||||
// &routes,
|
||||
// |r| r.leptos_path(),
|
||||
// &[
|
||||
// "/bar",
|
||||
// "/bar/",
|
||||
// "/baz/*any",
|
||||
// "/baz/:id",
|
||||
// "/baz/:id/",
|
||||
// "/baz/:name",
|
||||
// "/baz/:name/",
|
||||
// "/foo",
|
||||
// "/foo/",
|
||||
// ],
|
||||
// );
|
||||
//
|
||||
// // ... But leptos-actix has also reformatted "paths" to work for Actix.
|
||||
// assert_same(
|
||||
// &routes,
|
||||
// |r| r.path(),
|
||||
// &[
|
||||
// "/bar",
|
||||
// "/bar/",
|
||||
// "/baz/{id}",
|
||||
// "/baz/{id}/",
|
||||
// "/baz/{name}",
|
||||
// "/baz/{name}/",
|
||||
// "/baz/{tail:.*}",
|
||||
// "/foo",
|
||||
// "/foo/",
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// fn assert_same<'t, T, F, U>(
|
||||
// input: &'t Vec<T>,
|
||||
// mapper: F,
|
||||
// expected_sorted_values: &[U],
|
||||
// ) where
|
||||
// F: Fn(&'t T) -> U + 't,
|
||||
// U: Ord + std::fmt::Debug,
|
||||
// {
|
||||
// let mut values: Vec<U> = input.iter().map(mapper).collect();
|
||||
// values.sort();
|
||||
// assert_eq!(values, expected_sorted_values);
|
||||
// }
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
[package]
|
||||
name = "leptos_axum"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
version = { workspace = true }
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { version = "0.7", default-features = false, features = [
|
||||
"matched-path",
|
||||
axum = { version = "0.7.5", default-features = false, features = [
|
||||
"matched-path",
|
||||
] }
|
||||
futures = "0.3"
|
||||
http = "1"
|
||||
http-body-util = "0.1"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
http-body-util = "0.1.2"
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
server_fn = { workspace = true, features = ["axum-no-default"] }
|
||||
leptos_macro = { workspace = true, features = ["axum"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
parking_lot = "0.12"
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", default-features = false }
|
||||
tower = "0.4"
|
||||
tower-http = "0.5"
|
||||
tracing = "0.1"
|
||||
parking_lot = "0.12.3"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", default-features = false }
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.40"
|
||||
|
||||
[dev-dependencies]
|
||||
axum = "0.7"
|
||||
tokio = { version = "1", features = ["net"] }
|
||||
axum = "0.7.5"
|
||||
tokio = { version = "1.39", features = ["net", "rt-multi-thread"] }
|
||||
|
||||
[features]
|
||||
wasm = []
|
||||
|
||||
@@ -34,16 +34,22 @@
|
||||
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
extract::{FromRef, FromRequestParts, MatchedPath, State},
|
||||
extract::{FromRequestParts, MatchedPath},
|
||||
http::{
|
||||
header::{self, HeaderName, HeaderValue, ACCEPT, LOCATION, REFERER},
|
||||
request::Parts,
|
||||
HeaderMap, Method, Request, Response, StatusCode, Uri,
|
||||
HeaderMap, Method, Request, Response, StatusCode,
|
||||
},
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, patch, post, put},
|
||||
};
|
||||
#[cfg(feature = "default")]
|
||||
use axum::{
|
||||
extract::{FromRef, State},
|
||||
http::Uri,
|
||||
};
|
||||
use futures::{stream::once, Future, Stream, StreamExt};
|
||||
use hydration_context::SsrSharedContext;
|
||||
use leptos::{
|
||||
config::LeptosOptions,
|
||||
context::{provide_context, use_context},
|
||||
@@ -62,6 +68,7 @@ use leptos_router::{
|
||||
use parking_lot::RwLock;
|
||||
use server_fn::{redirect::REDIRECT_HEADER, ServerFnError};
|
||||
use std::{fmt::Debug, io, pin::Pin, sync::Arc};
|
||||
#[cfg(feature = "default")]
|
||||
use tower::ServiceExt;
|
||||
#[cfg(feature = "default")]
|
||||
use tower_http::services::ServeDir;
|
||||
@@ -95,7 +102,9 @@ impl ResponseParts {
|
||||
///
|
||||
/// If you provide your own handler, you will need to provide `ResponseOptions` via context
|
||||
/// yourself if you want to access it via context.
|
||||
/// ```rust,ignore
|
||||
/// ```
|
||||
/// use leptos::prelude::*;
|
||||
///
|
||||
/// #[server]
|
||||
/// pub async fn get_opts() -> Result<(), ServerFnError> {
|
||||
/// let opts = expect_context::<leptos_axum::ResponseOptions>();
|
||||
@@ -169,9 +178,33 @@ impl ExtendResponse for AxumResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an easy way to redirect the user from within a server function. Mimicking the Remix `redirect()`,
|
||||
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
|
||||
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
|
||||
/// Provides an easy way to redirect the user from within a server function.
|
||||
///
|
||||
/// Calling `redirect` in a server function will redirect the browser in three
|
||||
/// situations:
|
||||
/// 1. A server function that is calling in a [blocking
|
||||
/// resource](leptos::server::Resource::new_blocking).
|
||||
/// 2. A server function that is called from WASM running in the client (e.g., a dispatched action
|
||||
/// or a spawned `Future`).
|
||||
/// 3. A `<form>` submitted to the server function endpoint using default browser APIs (often due
|
||||
/// to using [`ActionForm`](leptos::form::ActionForm) without JS/WASM present.)
|
||||
///
|
||||
/// Using it with a non-blocking [`Resource`](leptos::server::Resource) will not work if you are using streaming rendering,
|
||||
/// as the response's headers will already have been sent by the time the server function calls `redirect()`.
|
||||
///
|
||||
/// ### Implementation
|
||||
///
|
||||
/// This sets the `Location` header to the URL given.
|
||||
///
|
||||
/// If the route or server function in which this is called is being accessed
|
||||
/// by an ordinary `GET` request or an HTML `<form>` without any enhancement, it also sets a
|
||||
/// status code of `302` for a temporary redirect. (This is determined by whether the `Accept`
|
||||
/// header contains `text/html` as it does for an ordinary navigation.)
|
||||
///
|
||||
/// Otherwise, it sets a custom header that indicates to the client that it should redirect,
|
||||
/// without actually setting the status code. This means that the client will not follow the
|
||||
/// redirect, and can therefore return the value of the server function and then handle
|
||||
/// the redirect with client-side routing.
|
||||
pub fn redirect(path: &str) {
|
||||
if let (Some(req), Some(res)) =
|
||||
(use_context::<Parts>(), use_context::<ResponseOptions>())
|
||||
@@ -228,7 +261,7 @@ pub fn generate_request_and_parts(
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, routing::post, Router};
|
||||
/// use leptos::*;
|
||||
/// use leptos::prelude::*;
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// # if false { // don't actually try to run a server in a doctest...
|
||||
@@ -312,10 +345,13 @@ async fn handle_server_fns_inner(
|
||||
) -> impl IntoResponse {
|
||||
use server_fn::middleware::Service;
|
||||
|
||||
let method = req.method().clone();
|
||||
let path = req.uri().path().to_string();
|
||||
let (req, parts) = generate_request_and_parts(req);
|
||||
|
||||
if let Some(mut service) = server_fn::axum::get_server_fn_service(&path) {
|
||||
if let Some(mut service) =
|
||||
server_fn::axum::get_server_fn_service(&path, method)
|
||||
{
|
||||
let owner = Owner::new();
|
||||
owner
|
||||
.with(|| {
|
||||
@@ -388,8 +424,7 @@ pub type PinnedHtmlStream =
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
/// use leptos::*;
|
||||
/// use leptos_config::get_configuration;
|
||||
/// use leptos::{config::get_configuration, prelude::*};
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
@@ -407,8 +442,7 @@ pub type PinnedHtmlStream =
|
||||
///
|
||||
/// // build our application with a route
|
||||
/// let app = Router::new().fallback(leptos_axum::render_app_to_stream(
|
||||
/// leptos_options,
|
||||
/// || view! { <MyApp/> },
|
||||
/// || { /* your application here */ },
|
||||
/// ));
|
||||
///
|
||||
/// // run our app with hyper
|
||||
@@ -476,8 +510,7 @@ where
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
/// use leptos::*;
|
||||
/// use leptos_config::get_configuration;
|
||||
/// use leptos::{config::get_configuration, prelude::*};
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
@@ -494,11 +527,9 @@ where
|
||||
/// let addr = leptos_options.site_addr.clone();
|
||||
///
|
||||
/// // build our application with a route
|
||||
/// let app =
|
||||
/// Router::new().fallback(leptos_axum::render_app_to_stream_in_order(
|
||||
/// leptos_options,
|
||||
/// || view! { <MyApp/> },
|
||||
/// ));
|
||||
/// let app = Router::new().fallback(
|
||||
/// leptos_axum::render_app_to_stream_in_order(|| view! { <MyApp/> }),
|
||||
/// );
|
||||
///
|
||||
/// // run our app with hyper
|
||||
/// let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
@@ -536,14 +567,25 @@ where
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```ignore
|
||||
/// async fn custom_handler(Path(id): Path<String>, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> Response{
|
||||
/// let handler = leptos_axum::render_app_to_stream_with_context((*options).clone(),
|
||||
/// || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || view! { <TodoApp/> }
|
||||
/// );
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// body::Body,
|
||||
/// extract::Path,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
/// use http::Request;
|
||||
/// use leptos::{config::LeptosOptions, context::provide_context, prelude::*};
|
||||
///
|
||||
/// async fn custom_handler(
|
||||
/// Path(id): Path<String>,
|
||||
/// req: Request<Body>,
|
||||
/// ) -> Response {
|
||||
/// let handler = leptos_axum::render_app_to_stream_with_context(
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || { /* your app here */ },
|
||||
/// );
|
||||
/// handler(req).await.into_response()
|
||||
/// }
|
||||
/// ```
|
||||
@@ -694,14 +736,25 @@ where
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```ignore
|
||||
/// async fn custom_handler(Path(id): Path<String>, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> Response{
|
||||
/// let handler = leptos_axum::render_app_to_stream_in_order_with_context((*options).clone(),
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || view! { <TodoApp/> }
|
||||
/// );
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// body::Body,
|
||||
/// extract::Path,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
/// use http::Request;
|
||||
/// use leptos::context::provide_context;
|
||||
///
|
||||
/// async fn custom_handler(
|
||||
/// Path(id): Path<String>,
|
||||
/// req: Request<Body>,
|
||||
/// ) -> Response {
|
||||
/// let handler = leptos_axum::render_app_to_stream_in_order_with_context(
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || { /* your application here */ },
|
||||
/// );
|
||||
/// handler(req).await.into_response()
|
||||
/// }
|
||||
/// ```
|
||||
@@ -834,8 +887,7 @@ fn provide_contexts(
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
/// use leptos::*;
|
||||
/// use leptos_config::get_configuration;
|
||||
/// use leptos::{config::get_configuration, prelude::*};
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
@@ -852,10 +904,8 @@ fn provide_contexts(
|
||||
/// let addr = leptos_options.site_addr.clone();
|
||||
///
|
||||
/// // build our application with a route
|
||||
/// let app = Router::new().fallback(leptos_axum::render_app_async(
|
||||
/// leptos_options,
|
||||
/// || view! { <MyApp/> },
|
||||
/// ));
|
||||
/// let app = Router::new()
|
||||
/// .fallback(leptos_axum::render_app_async(|| view! { <MyApp/> }));
|
||||
///
|
||||
/// // run our app with hyper
|
||||
/// // `axum::Server` is a re-export of `hyper::Server`
|
||||
@@ -896,14 +946,25 @@ where
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```ignore
|
||||
/// async fn custom_handler(Path(id): Path<String>, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> Response{
|
||||
/// let handler = leptos_axum::render_app_async_with_context((*options).clone(),
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || view! { <TodoApp/> }
|
||||
/// );
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// body::Body,
|
||||
/// extract::Path,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
/// use http::Request;
|
||||
/// use leptos::context::provide_context;
|
||||
///
|
||||
/// async fn custom_handler(
|
||||
/// Path(id): Path<String>,
|
||||
/// req: Request<Body>,
|
||||
/// ) -> Response {
|
||||
/// let handler = leptos_axum::render_app_async_with_context(
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || { /* your application here */ },
|
||||
/// );
|
||||
/// handler(req).await.into_response()
|
||||
/// }
|
||||
/// ```
|
||||
@@ -950,14 +1011,25 @@ where
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure. An example is below
|
||||
/// ```ignore
|
||||
/// async fn custom_handler(Path(id): Path<String>, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> Response{
|
||||
/// let handler = leptos_axum::render_app_async_with_context((*options).clone(),
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || view! { <TodoApp/> }
|
||||
/// );
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// body::Body,
|
||||
/// extract::Path,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
/// use http::Request;
|
||||
/// use leptos::context::provide_context;
|
||||
///
|
||||
/// async fn custom_handler(
|
||||
/// Path(id): Path<String>,
|
||||
/// req: Request<Body>,
|
||||
/// ) -> Response {
|
||||
/// let handler = leptos_axum::render_app_async_with_context(
|
||||
/// move || {
|
||||
/// provide_context(id.clone());
|
||||
/// },
|
||||
/// || { /* your application here */ },
|
||||
/// );
|
||||
/// handler(req).await.into_response()
|
||||
/// }
|
||||
/// ```
|
||||
@@ -1166,7 +1238,7 @@ where
|
||||
{
|
||||
init_executor();
|
||||
|
||||
let owner = Owner::new_root(None);
|
||||
let owner = Owner::new_root(Some(Arc::new(SsrSharedContext::new())));
|
||||
let routes = owner
|
||||
.with(|| {
|
||||
// stub out a path for now
|
||||
@@ -1676,15 +1748,19 @@ where
|
||||
///
|
||||
/// Any error that occurs during extraction is converted to a [`ServerFnError`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // MyQuery is some type that implements `Deserialize + Serialize`
|
||||
/// #[server]
|
||||
/// pub async fn query_extract() -> Result<MyQuery, ServerFnError> {
|
||||
/// use axum::{extract::Query, http::Method};
|
||||
/// use leptos_axum::*;
|
||||
/// let Query(query) = extract().await?;
|
||||
/// ```rust
|
||||
/// use leptos::prelude::*;
|
||||
///
|
||||
/// Ok(query)
|
||||
/// #[server]
|
||||
/// pub async fn request_method() -> Result<String, ServerFnError> {
|
||||
/// use http::Method;
|
||||
/// use leptos_axum::extract;
|
||||
///
|
||||
/// // you can extract anything that a regular Axum extractor can extract
|
||||
/// // from the head (not from the body of the request)
|
||||
/// let method: Method = extract().await?;
|
||||
///
|
||||
/// Ok(format!("{method:?}"))
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract<T>() -> Result<T, ServerFnError>
|
||||
@@ -1702,18 +1778,6 @@ where
|
||||
/// therefore be used in an extractor. The compiler can often infer this type.
|
||||
///
|
||||
/// Any error that occurs during extraction is converted to a [`ServerFnError`].
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // MyQuery is some type that implements `Deserialize + Serialize`
|
||||
/// #[server]
|
||||
/// pub async fn query_extract() -> Result<MyQuery, ServerFnError> {
|
||||
/// use axum::{extract::Query, http::Method};
|
||||
/// use leptos_axum::*;
|
||||
/// let Query(query) = extract().await?;
|
||||
///
|
||||
/// Ok(query)
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract_with_state<T, S>(state: &S) -> Result<T, ServerFnError>
|
||||
where
|
||||
T: Sized + FromRequestParts<S>,
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
[package]
|
||||
name = "leptos_integration_utils"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities to help build server integrations for the Leptos web framework."
|
||||
version = { workspace = true }
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
futures = "0.3.30"
|
||||
hydration_context = { workspace = true }
|
||||
leptos = { workspace = true, features = ["nonce"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_config = { workspace = true }
|
||||
reactive_graph = { workspace = true, features = ["sandboxed-arenas"] }
|
||||
tracing = "0.1.37"
|
||||
tracing = "0.1.40"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -1,57 +1,62 @@
|
||||
[package]
|
||||
name = "leptos"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces."
|
||||
readme = "../README.md"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
throw_error = { workspace = true }
|
||||
any_spawner = { workspace = true, features = ["wasm-bindgen"] }
|
||||
base64 = { version = "0.22", optional = true }
|
||||
cfg-if = "1"
|
||||
base64 = { version = "0.22.1", optional = true }
|
||||
cfg-if = "1.0"
|
||||
hydration_context = { workspace = true }
|
||||
either_of = { workspace = true }
|
||||
leptos_dom = { workspace = true }
|
||||
leptos_hot_reload = { workspace = true }
|
||||
leptos_macro = { workspace = true }
|
||||
leptos_server = { workspace = true, features = ["tachys"] }
|
||||
leptos_config = { workspace = true }
|
||||
leptos-spin-macro = { version = "0.1", optional = true }
|
||||
leptos-spin-macro = { version = "0.2.0", optional = true }
|
||||
oco_ref = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
paste = "1"
|
||||
rand = { version = "0.8", optional = true }
|
||||
paste = "1.0"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = "1"
|
||||
rustc-hash = "2.0"
|
||||
tachys = { workspace = true, features = ["reactive_graph", "oco"] }
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.18"
|
||||
typed-builder-macro = "0.18"
|
||||
serde = "1"
|
||||
serde_json = { version = "1", optional = true }
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1.40"
|
||||
typed-builder = "0.19.1"
|
||||
typed-builder-macro = "0.19.1"
|
||||
serde = "1.0"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
server_fn = { workspace = true, features = [
|
||||
"form-redirects",
|
||||
"browser",
|
||||
"url",
|
||||
] }
|
||||
web-sys = { version = "0.3.63", features = [
|
||||
web-sys = { version = "0.3.70", features = [
|
||||
"ShadowRoot",
|
||||
"ShadowRootInit",
|
||||
"ShadowRootMode",
|
||||
] }
|
||||
wasm-bindgen = "0.2"
|
||||
serde_qs = "0.12.0"
|
||||
slotmap = "1.0.7"
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde_qs = "0.13.0"
|
||||
slotmap = "1.0"
|
||||
futures = "0.3.30"
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
hydration = ["reactive_graph/hydration", "leptos_server/hydration", "hydration_context/browser"]
|
||||
hydration = [
|
||||
"reactive_graph/hydration",
|
||||
"leptos_server/hydration",
|
||||
"hydration_context/browser",
|
||||
]
|
||||
csr = ["leptos_macro/csr", "reactive_graph/effects"]
|
||||
hydrate = [
|
||||
"leptos_macro/hydrate",
|
||||
@@ -68,23 +73,16 @@ ssr = [
|
||||
"hydration",
|
||||
"tachys/ssr",
|
||||
]
|
||||
nightly = ["leptos_dom/nightly", "leptos_macro/nightly", "tachys/nightly"]
|
||||
nightly = ["leptos_macro/nightly", "reactive_graph/nightly", "tachys/nightly"]
|
||||
rkyv = ["server_fn/rkyv"]
|
||||
tracing = [
|
||||
"reactive_graph/tracing",
|
||||
"tachys/tracing",
|
||||
] #, "leptos_macro/tracing", "leptos_dom/tracing"]
|
||||
nonce = ["base64", "leptos_dom/nonce", "rand"]
|
||||
nonce = ["base64", "rand"]
|
||||
spin = ["leptos-spin-macro"]
|
||||
experimental-islands = [
|
||||
"leptos_dom/experimental-islands",
|
||||
"leptos_macro/experimental-islands",
|
||||
"dep:serde_json",
|
||||
]
|
||||
trace-component-props = [
|
||||
"leptos_dom/trace-component-props",
|
||||
"leptos_macro/trace-component-props",
|
||||
]
|
||||
experimental-islands = ["leptos_macro/experimental-islands", "dep:serde_json"]
|
||||
trace-component-props = ["leptos_macro/trace-component-props"]
|
||||
delegation = ["tachys/delegation"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -6,16 +6,17 @@
|
||||
//! Callbacks can be created manually from any function or closure, but the easiest way
|
||||
//! to create them is to use `#[prop(into)]]` when defining a component.
|
||||
//! ```
|
||||
//! # use leptos::*;
|
||||
//! use leptos::prelude::*;
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn MyComponent(
|
||||
//! #[prop(into)] render_number: Callback<i32, String>,
|
||||
//! ) -> impl IntoView {
|
||||
//! view! {
|
||||
//! <div>
|
||||
//! {render_number.call(1)}
|
||||
//! {render_number.run(1)}
|
||||
//! // callbacks can be called multiple times
|
||||
//! {render_number.call(42)}
|
||||
//! {render_number.run(42)}
|
||||
//! </div>
|
||||
//! }
|
||||
//! }
|
||||
@@ -30,28 +31,28 @@
|
||||
//! *Notes*:
|
||||
//! - The `render_number` prop can receive any type that implements `Fn(i32) -> String`.
|
||||
//! - Callbacks are most useful when you want optional generic props.
|
||||
//! - All callbacks implement the [`Callable`] trait, and can be invoked with `my_callback.call(input)`. On nightly, you can even do `my_callback(input)`
|
||||
//! - All callbacks implement the [`Callable`] trait, and can be invoked with `my_callback.run(input)`.
|
||||
//! - The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
|
||||
//!
|
||||
//! # Types
|
||||
//! This modules implements 2 callback types:
|
||||
//! - [`Callback`]
|
||||
//! - [`SyncCallback`]
|
||||
//! - [`UnsyncCallback`]
|
||||
//!
|
||||
//! Use `SyncCallback` when you want the function to be `Sync` and `Send`.
|
||||
//! Use `SyncCallback` if the function is not `Sync` and `Send`.
|
||||
|
||||
use reactive_graph::owner::StoredValue;
|
||||
use reactive_graph::owner::{LocalStorage, StoredValue};
|
||||
use std::{fmt, rc::Rc, sync::Arc};
|
||||
|
||||
/// A wrapper trait for calling callbacks.
|
||||
pub trait Callable<In: 'static, Out: 'static = ()> {
|
||||
/// calls the callback with the specified argument.
|
||||
fn call(&self, input: In) -> Out;
|
||||
fn run(&self, input: In) -> Out;
|
||||
}
|
||||
|
||||
/// A callback type that is not required to be `Send + Sync`.
|
||||
pub struct UnsyncCallback<In: 'static, Out: 'static = ()>(
|
||||
Rc<dyn Fn(In) -> Out>,
|
||||
StoredValue<Rc<dyn Fn(In) -> Out>, LocalStorage>,
|
||||
);
|
||||
|
||||
impl<In> fmt::Debug for UnsyncCallback<In> {
|
||||
@@ -60,9 +61,11 @@ impl<In> fmt::Debug for UnsyncCallback<In> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out> Copy for UnsyncCallback<In, Out> {}
|
||||
|
||||
impl<In, Out> Clone for UnsyncCallback<In, Out> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Rc::clone(&self.0))
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,92 +75,40 @@ impl<In, Out> UnsyncCallback<In, Out> {
|
||||
where
|
||||
F: Fn(In) -> Out + 'static,
|
||||
{
|
||||
Self(Rc::new(f))
|
||||
Self(StoredValue::new_local(Rc::new(f)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static, Out: 'static> Callable<In, Out> for UnsyncCallback<In, Out> {
|
||||
fn call(&self, input: In) -> Out {
|
||||
(self.0)(input)
|
||||
fn run(&self, input: In) -> Out {
|
||||
self.0.with_value(|fun| fun(input))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_fn {
|
||||
($ty:ident) => {
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
impl<F, In, T, Out> From<F> for $ty<In, Out>
|
||||
where
|
||||
F: Fn(In) -> T + Send + Sync + 'static,
|
||||
T: Into<Out> + 'static,
|
||||
In: Send + Sync + 'static,
|
||||
{
|
||||
fn from(f: F) -> Self {
|
||||
Self::new(move |x| f(x).into())
|
||||
}
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
#[cfg(feature = "nightly")]
|
||||
auto trait [<NotRaw $ty>] {}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<A, B> ![<NotRaw $ty>] for $ty<A, B> {}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<F, In, T, Out> From<F> for $ty<In, Out>
|
||||
where
|
||||
F: Fn(In) -> T + Send + Sync + [<NotRaw $ty>] + 'static,
|
||||
T: Into<Out> + 'static,
|
||||
In: Send + Sync + 'static
|
||||
{
|
||||
fn from(f: F) -> Self {
|
||||
Self::new(move |x| f(x).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO
|
||||
//impl_from_fn!(UnsyncCallback);
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<In, Out> FnOnce<(In,)> for UnsyncCallback<In, Out> {
|
||||
type Output = Out;
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (In,)) -> Self::Output {
|
||||
Callable::call(&self, args.0)
|
||||
impl<F, In, T, Out> From<F> for UnsyncCallback<In, Out>
|
||||
where
|
||||
F: Fn(In) -> T + 'static,
|
||||
T: Into<Out> + 'static,
|
||||
In: 'static,
|
||||
{
|
||||
fn from(f: F) -> Self {
|
||||
Self::new(move |x| f(x).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<In, Out> FnMut<(In,)> for UnsyncCallback<In, Out> {
|
||||
extern "rust-call" fn call_mut(&mut self, args: (In,)) -> Self::Output {
|
||||
Callable::call(&*self, args.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<In, Out> Fn<(In,)> for UnsyncCallback<In, Out> {
|
||||
extern "rust-call" fn call(&self, args: (In,)) -> Self::Output {
|
||||
Callable::call(self, args.0)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO update these docs to swap the two
|
||||
/// Callbacks define a standard way to store functions and closures.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::{Callable, Callback};
|
||||
/// # use leptos::prelude::*;
|
||||
/// # use leptos::callback::{Callable, Callback};
|
||||
/// #[component]
|
||||
/// fn MyComponent(
|
||||
/// #[prop(into)] render_number: Callback<i32, String>,
|
||||
/// ) -> impl IntoView {
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// {render_number.call(42)}
|
||||
/// {render_number.run(42)}
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
@@ -182,7 +133,7 @@ impl<In, Out> fmt::Debug for Callback<In, Out> {
|
||||
}
|
||||
|
||||
impl<In, Out> Callable<In, Out> for Callback<In, Out> {
|
||||
fn call(&self, input: In) -> Out {
|
||||
fn run(&self, input: In) -> Out {
|
||||
self.0
|
||||
.try_with_value(|f| f(input))
|
||||
.expect("called a callback that has been disposed")
|
||||
@@ -197,6 +148,17 @@ impl<In, Out> Clone for Callback<In, Out> {
|
||||
|
||||
impl<In, Out> Copy for Callback<In, Out> {}
|
||||
|
||||
impl<F, In, T, Out> From<F> for Callback<In, Out>
|
||||
where
|
||||
F: Fn(In) -> T + Send + Sync + 'static,
|
||||
T: Into<Out> + 'static,
|
||||
In: Send + Sync + 'static,
|
||||
{
|
||||
fn from(f: F) -> Self {
|
||||
Self::new(move |x| f(x).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<In: 'static, Out: 'static> Callback<In, Out> {
|
||||
/// Creates a new callback from the given function.
|
||||
pub fn new<F>(fun: F) -> Self
|
||||
@@ -207,114 +169,32 @@ impl<In: 'static, Out: 'static> Callback<In, Out> {
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_fn!(Callback);
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<In, Out> FnOnce<(In,)> for Callback<In, Out>
|
||||
where
|
||||
In: Send + Sync + 'static,
|
||||
Out: 'static,
|
||||
{
|
||||
type Output = Out;
|
||||
|
||||
extern "rust-call" fn call_once(self, args: (In,)) -> Self::Output {
|
||||
Callable::call(&self, args.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<In, Out> FnMut<(In,)> for Callback<In, Out>
|
||||
where
|
||||
In: Send + Sync + 'static,
|
||||
Out: 'static,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, args: (In,)) -> Self::Output {
|
||||
Callable::call(&*self, args.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<In, Out> Fn<(In,)> for Callback<In, Out>
|
||||
where
|
||||
In: Send + Sync + 'static,
|
||||
Out: 'static,
|
||||
{
|
||||
extern "rust-call" fn call(&self, args: (In,)) -> Self::Output {
|
||||
Callable::call(self, args.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
callback::{Callback, UnsyncCallback},
|
||||
create_runtime,
|
||||
};
|
||||
use crate::callback::{Callback, UnsyncCallback};
|
||||
|
||||
struct NoClone {}
|
||||
|
||||
#[test]
|
||||
fn clone_callback() {
|
||||
let rt = create_runtime();
|
||||
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_unsync_callback() {
|
||||
let callback =
|
||||
UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
rt.dispose();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_sync_callback() {
|
||||
let rt = create_runtime();
|
||||
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
rt.dispose();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn callback_from() {
|
||||
let rt = create_runtime();
|
||||
let _callback: UnsyncCallback<(), String> = (|()| "test").into();
|
||||
rt.dispose();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn callback_from_html() {
|
||||
let rt = create_runtime();
|
||||
use leptos::{
|
||||
html::{AnyElement, HtmlElement},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
let _callback: UnsyncCallback<String, HtmlElement<AnyElement>> =
|
||||
(|x: String| {
|
||||
view! { <h1>{x}</h1> }
|
||||
})
|
||||
.into();
|
||||
rt.dispose();
|
||||
fn runback_from() {
|
||||
let _callback: Callback<(), String> = (|()| "test").into();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_callback_from() {
|
||||
let rt = create_runtime();
|
||||
let _callback: Callback<(), String> = (|()| "test").into();
|
||||
rt.dispose();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_callback_from_html() {
|
||||
use leptos::{
|
||||
html::{AnyElement, HtmlElement},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
let rt = create_runtime();
|
||||
|
||||
let _callback: Callback<String, HtmlElement<AnyElement>> =
|
||||
(|x: String| {
|
||||
view! { <h1>{x}</h1> }
|
||||
})
|
||||
.into();
|
||||
|
||||
rt.dispose();
|
||||
let _callback: UnsyncCallback<(), String> = (|()| "test").into();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,70 +47,44 @@ type BoxedChildrenFn = Box<dyn Fn() -> AnyView<Dom> + Send>;
|
||||
/// to know exactly what children type the component expects. This is used internally by the
|
||||
/// `view!` macro implementation, and can also be used explicitly when using the builder syntax.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Without ToChildren
|
||||
/// Different component types take different types for their `children` prop, some of which cannot
|
||||
/// be directly constructed. Using `ToChildren` allows the component user to pass children without
|
||||
/// explicity constructing the correct type.
|
||||
///
|
||||
/// Without [ToChildren], consumers need to explicitly provide children using the type expected
|
||||
/// by the component. For example, [Provider][crate::Provider]'s children need to wrapped in
|
||||
/// a [Box], while [Show][crate::Show]'s children need to be wrapped in an [Rc].
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::{ProviderProps, ShowProps};
|
||||
/// # use leptos_dom::html::p;
|
||||
/// # use leptos_dom::IntoView;
|
||||
/// # use leptos::prelude::*;
|
||||
/// # use leptos::html::p;
|
||||
/// # use leptos::IntoView;
|
||||
/// # use leptos_macro::component;
|
||||
/// # use std::rc::Rc;
|
||||
/// #
|
||||
/// #[component]
|
||||
/// fn App() -> impl IntoView {
|
||||
/// (
|
||||
/// ProviderProps::builder()
|
||||
/// .children(Box::new(|| p().child("Foo").into_view().into()))
|
||||
/// // ...
|
||||
/// # .value("Foo")
|
||||
/// # .build(),
|
||||
/// ShowProps::builder()
|
||||
/// .children(Rc::new(|| p().child("Foo").into_view().into()))
|
||||
/// // ...
|
||||
/// # .when(|| true)
|
||||
/// # .fallback(|| p().child("foo"))
|
||||
/// # .build(),
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With ToChildren
|
||||
///
|
||||
/// With [ToChildren], consumers don't need to know exactly which type a component uses for
|
||||
/// its children.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::{ProviderProps, ShowProps};
|
||||
/// # use leptos_dom::html::p;
|
||||
/// # use leptos_dom::IntoView;
|
||||
/// # use leptos_macro::component;
|
||||
/// # use std::rc::Rc;
|
||||
/// # use leptos::ToChildren;
|
||||
/// #
|
||||
/// # use leptos::children::ToChildren;
|
||||
/// use leptos::context::{Provider, ProviderProps};
|
||||
/// use leptos::control_flow::{Show, ShowProps};
|
||||
///
|
||||
/// #[component]
|
||||
/// fn App() -> impl IntoView {
|
||||
/// (
|
||||
/// Provider(
|
||||
/// ProviderProps::builder()
|
||||
/// .children(ToChildren::to_children(|| {
|
||||
/// p().child("Foo").into_view().into()
|
||||
/// p().child("Foo")
|
||||
/// }))
|
||||
/// // ...
|
||||
/// # .value("Foo")
|
||||
/// # .build(),
|
||||
/// ShowProps::builder()
|
||||
/// .value("Foo")
|
||||
/// .build(),
|
||||
/// ),
|
||||
/// Show(
|
||||
/// ShowProps::builder()
|
||||
/// .children(ToChildren::to_children(|| {
|
||||
/// p().child("Foo").into_view().into()
|
||||
/// p().child("Foo")
|
||||
/// }))
|
||||
/// // ...
|
||||
/// # .when(|| true)
|
||||
/// # .fallback(|| p().child("foo"))
|
||||
/// # .build(),
|
||||
/// .when(|| true)
|
||||
/// .fallback(|| p().child("foo"))
|
||||
/// .build(),
|
||||
/// )
|
||||
/// )
|
||||
/// }
|
||||
pub trait ToChildren<F> {
|
||||
|
||||
@@ -9,7 +9,7 @@ use reactive_graph::{
|
||||
traits::{Get, Update, With, WithUntracked},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
@@ -22,6 +22,29 @@ use tachys::{
|
||||
};
|
||||
use throw_error::{Error, ErrorHook, ErrorId};
|
||||
|
||||
/// When you render a `Result<_, _>` in your view, in the `Err` case it will
|
||||
/// render nothing, and search up through the view tree for an `<ErrorBoundary/>`.
|
||||
/// This component lets you define a fallback that should be rendered in that
|
||||
/// error case, allowing you to handle errors within a section of the interface.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
/// #[component]
|
||||
/// pub fn ErrorBoundaryExample() -> impl IntoView {
|
||||
/// let (value, set_value) = signal(Ok(0));
|
||||
/// let on_input =
|
||||
/// move |ev| set_value.set(event_target_value(&ev).parse::<i32>());
|
||||
///
|
||||
/// view! {
|
||||
/// <input type="text" on:input=on_input/>
|
||||
/// <ErrorBoundary
|
||||
/// fallback=move |_| view! { <p class="error">"Enter a valid number."</p>}
|
||||
/// >
|
||||
/// <p>"Value is: " {move || value.get()}</p>
|
||||
/// </ErrorBoundary>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Beginner's Tip: ErrorBoundary Requires Your Error To Implement std::error::Error.
|
||||
/// `ErrorBoundary` requires your `Result<T,E>` to implement [IntoView](https://docs.rs/leptos/latest/leptos/trait.IntoView.html).
|
||||
@@ -72,12 +95,11 @@ where
|
||||
});
|
||||
let hook = hook as Arc<dyn ErrorHook>;
|
||||
|
||||
// provide the error hook and render children
|
||||
// TODO unset this outside the ErrorBoundary
|
||||
throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let _guard = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let children = children.into_inner()();
|
||||
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children,
|
||||
@@ -87,8 +109,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ErrorBoundaryView<Chil, FalFn, Rndr> {
|
||||
hook: Arc<dyn ErrorHook>,
|
||||
boundary_id: SerializedDataId,
|
||||
errors_empty: ArcMemo<bool>,
|
||||
children: Chil,
|
||||
@@ -145,11 +167,14 @@ where
|
||||
type State = RenderEffect<ErrorBoundaryViewState<Chil::State, Fal::State>>;
|
||||
|
||||
fn build(mut self) -> Self::State {
|
||||
let hook = Arc::clone(&self.hook);
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let mut children = Some(self.children.build());
|
||||
RenderEffect::new(
|
||||
move |prev: Option<
|
||||
ErrorBoundaryViewState<Chil::State, Fal::State>,
|
||||
>| {
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
if let Some(mut state) = prev {
|
||||
match (self.errors_empty.get(), &mut state.fallback) {
|
||||
// no errors, and was showing fallback
|
||||
@@ -216,6 +241,7 @@ where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
let ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children,
|
||||
@@ -224,6 +250,7 @@ where
|
||||
rndr,
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children: children.add_any_attr(attr.into_cloneable_owned()),
|
||||
@@ -252,6 +279,7 @@ where
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
let ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children,
|
||||
@@ -260,6 +288,7 @@ where
|
||||
..
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children: children.resolve().await,
|
||||
@@ -277,6 +306,7 @@ where
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let _hook = throw_error::set_error_hook(self.hook);
|
||||
let mut new_buf = String::with_capacity(Chil::MIN_LENGTH);
|
||||
let mut new_pos = *position;
|
||||
self.children.to_html_with_buf(
|
||||
@@ -309,6 +339,7 @@ where
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let _hook = throw_error::set_error_hook(self.hook);
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let mut new_buf = StreamBuilder::new(buf.clone_id());
|
||||
let mut new_pos = *position;
|
||||
@@ -319,10 +350,6 @@ where
|
||||
mark_branches,
|
||||
);
|
||||
|
||||
if let Some(sc) = Owner::current_shared_context() {
|
||||
sc.seal_errors(&self.boundary_id);
|
||||
}
|
||||
|
||||
// any thrown errors would've been caught here
|
||||
if self.errors.with_untracked(|map| map.is_empty()) {
|
||||
buf.append(new_buf);
|
||||
@@ -345,12 +372,14 @@ where
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let mut children = Some(self.children);
|
||||
let hook = Arc::clone(&self.hook);
|
||||
let cursor = cursor.to_owned();
|
||||
let position = position.to_owned();
|
||||
RenderEffect::new(
|
||||
move |prev: Option<
|
||||
ErrorBoundaryViewState<Chil::State, Fal::State>,
|
||||
>| {
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
if let Some(mut state) = prev {
|
||||
match (self.errors_empty.get(), &mut state.fallback) {
|
||||
// no errors, and was showing fallback
|
||||
@@ -424,7 +453,10 @@ impl ErrorBoundaryErrorHook {
|
||||
impl ErrorHook for ErrorBoundaryErrorHook {
|
||||
fn throw(&self, error: Error) -> ErrorId {
|
||||
// generate a unique ID
|
||||
let key = ErrorId::default(); // TODO unique ID...
|
||||
let key: ErrorId = Owner::current_shared_context()
|
||||
.map(|sc| sc.next_id())
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
// register it with the shared context, so that it can be serialized from server to client
|
||||
// as needed
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::into_view::IntoView;
|
||||
use leptos_macro::component;
|
||||
use reactive_graph::owner::Owner;
|
||||
use reactive_graph::{
|
||||
owner::Owner,
|
||||
signal::{ArcRwSignal, ReadSignal},
|
||||
traits::Set,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
use tachys::{reactive_graph::OwnedView, view::keyed::keyed};
|
||||
|
||||
@@ -10,7 +14,7 @@ use tachys::{reactive_graph::OwnedView, view::keyed::keyed};
|
||||
/// as it avoids re-creating DOM nodes that are not being changed.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// struct Counter {
|
||||
@@ -68,10 +72,88 @@ where
|
||||
// a) the reactive owner for each row will not be cleared when the whole list updates
|
||||
// b) context provided in each row will not wipe out the others
|
||||
let parent = Owner::current().expect("no reactive owner");
|
||||
let children = move |child| {
|
||||
let children = move |_, child| {
|
||||
let owner = parent.with(Owner::new);
|
||||
let view = owner.with(|| children(child));
|
||||
OwnedView::new_with_owner(view, owner)
|
||||
(|_| {}, OwnedView::new_with_owner(view, owner))
|
||||
};
|
||||
move || keyed(each(), key.clone(), children.clone())
|
||||
}
|
||||
|
||||
/// Iterates over children and displays them, keyed by the `key` function given.
|
||||
///
|
||||
/// Compared with For, it has an additional index parameter, which can be used to obtain the current index in real time.
|
||||
///
|
||||
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { ... })...`,
|
||||
/// as it avoids re-creating DOM nodes that are not being changed.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// struct Counter {
|
||||
/// id: usize,
|
||||
/// count: RwSignal<i32>
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Counters() -> impl IntoView {
|
||||
/// let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
|
||||
///
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <ForEnumerate
|
||||
/// // a function that returns the items we're iterating over; a signal is fine
|
||||
/// each=move || counters.get()
|
||||
/// // a unique key for each item
|
||||
/// key=|counter| counter.id
|
||||
/// // renders each item to a view
|
||||
/// children={move |index: ReadSignal<usize>, counter: Counter| {
|
||||
/// view! {
|
||||
/// <button>{move || index.get()} ". Value: " {move || counter.count.get()}</button>
|
||||
/// }
|
||||
/// }}
|
||||
/// />
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
#[component]
|
||||
pub fn ForEnumerate<IF, I, T, EF, N, KF, K>(
|
||||
/// Items over which the component should iterate.
|
||||
each: IF,
|
||||
/// A key function that will be applied to each item.
|
||||
key: KF,
|
||||
/// A function that takes the index and the item, and returns the view that will be displayed for each item.
|
||||
children: EF,
|
||||
) -> impl IntoView
|
||||
where
|
||||
IF: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = T> + Send + 'static,
|
||||
EF: Fn(ReadSignal<usize>, T) -> N + Send + Clone + 'static,
|
||||
N: IntoView + 'static,
|
||||
KF: Fn(&T) -> K + Send + Clone + 'static,
|
||||
K: Eq + Hash + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
// this takes the owner of the For itself
|
||||
// this will end up with N + 1 children
|
||||
// 1) the effect for the `move || keyed(...)` updates
|
||||
// 2) an owner for each child
|
||||
//
|
||||
// this means
|
||||
// a) the reactive owner for each row will not be cleared when the whole list updates
|
||||
// b) context provided in each row will not wipe out the others
|
||||
let parent = Owner::current().expect("no reactive owner");
|
||||
let children = move |index, child| {
|
||||
let owner = parent.with(Owner::new);
|
||||
let (index, set_index) = ArcRwSignal::new(index).split();
|
||||
let view = owner.with(|| children(index.into(), child));
|
||||
(
|
||||
move |index| set_index.set(index),
|
||||
OwnedView::new_with_owner(view, owner),
|
||||
)
|
||||
};
|
||||
move || keyed(each(), key.clone(), children.clone())
|
||||
}
|
||||
@@ -80,25 +162,57 @@ where
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use leptos_macro::view;
|
||||
use tachys::{
|
||||
html::element::HtmlElement, prelude::ElementChild,
|
||||
renderer::mock_dom::MockDom, view::Render,
|
||||
};
|
||||
use tachys::{html::element::HtmlElement, prelude::ElementChild};
|
||||
|
||||
#[test]
|
||||
fn creates_list() {
|
||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
let list: HtmlElement<_, _, _, MockDom> = view! {
|
||||
<ol>
|
||||
<For each=move || values.get() key=|i| *i let:i>
|
||||
<li>{i}</li>
|
||||
</For>
|
||||
</ol>
|
||||
};
|
||||
let list = list.build();
|
||||
assert_eq!(
|
||||
list.el.to_debug_html(),
|
||||
"<ol><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ol>"
|
||||
);
|
||||
Owner::new().with(|| {
|
||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<ol>
|
||||
<For each=move || values.get() key=|i| *i let:i>
|
||||
<li>{i}</li>
|
||||
</For>
|
||||
</ol>
|
||||
};
|
||||
assert_eq!(
|
||||
list.to_html(),
|
||||
"<ol><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><!></\
|
||||
ol>"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creates_list_enumerate() {
|
||||
Owner::new().with(|| {
|
||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<ol>
|
||||
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
||||
<li>{move || index.get()}"-"{i}</li>
|
||||
</ForEnumerate>
|
||||
</ol>
|
||||
};
|
||||
assert_eq!(
|
||||
list.to_html(),
|
||||
"<ol><li>0<!>-<!>1</li><li>1<!>-<!>2</li><li>2<!>-<!>3</li><li>3\
|
||||
<!>-<!>4</li><li>4<!>-<!>5</li><!></ol>"
|
||||
);
|
||||
|
||||
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<ol>
|
||||
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
||||
<li>{move || index.get()}"-"{i}</li>
|
||||
</ForEnumerate>
|
||||
</ol>
|
||||
};
|
||||
values.set(vec![5, 4, 1, 2, 3]);
|
||||
assert_eq!(
|
||||
list.to_html(),
|
||||
"<ol><li>0<!>-<!>5</li><li>1<!>-<!>4</li><li>2<!>-<!>1</li><li>3\
|
||||
<!>-<!>2</li><li>4<!>-<!>3</li><!></ol>"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ use web_sys::{
|
||||
/// should make use of indexing notation of `serde_qs`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # use leptos_router::*;
|
||||
/// # use leptos::prelude::*;
|
||||
/// use leptos::form::ActionForm;
|
||||
///
|
||||
/// #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
/// struct HeftyData {
|
||||
@@ -45,7 +45,7 @@ use web_sys::{
|
||||
///
|
||||
/// #[component]
|
||||
/// fn ComplexInput() -> impl IntoView {
|
||||
/// let submit = Action::<VeryImportantFn, _>::server();
|
||||
/// let submit = ServerAction::<VeryImportantFn>::new();
|
||||
///
|
||||
/// view! {
|
||||
/// <ActionForm action=submit>
|
||||
@@ -145,10 +145,6 @@ where
|
||||
} else {
|
||||
Either::Right(action_form)
|
||||
}
|
||||
// TODO add other attributes
|
||||
/*for (attr_name, attr_value) in attributes {
|
||||
action_form = action_form.attr(attr_name, attr_value);
|
||||
}*/
|
||||
}
|
||||
|
||||
/// Automatically turns a server [MultiAction](leptos_server::MultiAction) into an HTML
|
||||
@@ -216,10 +212,6 @@ where
|
||||
} else {
|
||||
Either::Right(action_form)
|
||||
}
|
||||
// TODO add other attributes
|
||||
/*for (attr_name, attr_value) in attributes {
|
||||
action_form = action_form.attr(attr_name, attr_value);
|
||||
}*/
|
||||
}
|
||||
|
||||
/// Resolves a redirect location to an (absolute) URL.
|
||||
|
||||
@@ -24,12 +24,13 @@ pub fn AutoReload(
|
||||
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
|
||||
};
|
||||
|
||||
let script = include_str!("reload_script.js");
|
||||
view! {
|
||||
<script nonce=nonce>
|
||||
{format!("{script}({reload_port:?}, {protocol})")}
|
||||
</script>
|
||||
}
|
||||
let script = format!(
|
||||
"(function (reload_port, protocol) {{ {} {} }})({reload_port:?}, \
|
||||
{protocol})",
|
||||
leptos_hot_reload::HOT_RELOAD_JS,
|
||||
include_str!("reload_script.js")
|
||||
);
|
||||
view! { <script nonce=nonce>{script}</script> }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,6 +50,9 @@ pub fn HydrationScripts(
|
||||
#[cfg(not(feature = "nonce"))]
|
||||
let nonce = None::<String>;
|
||||
let script = if islands {
|
||||
if let Some(sc) = Owner::current_shared_context() {
|
||||
sc.set_is_hydrating(false);
|
||||
}
|
||||
include_str!("./island_script.js")
|
||||
} else {
|
||||
include_str!("./hydration_script.js")
|
||||
@@ -56,7 +60,13 @@ pub fn HydrationScripts(
|
||||
|
||||
view! {
|
||||
<link rel="modulepreload" href=format!("/{pkg_path}/{output_name}.js") nonce=nonce.clone()/>
|
||||
<link rel="preload" href=format!("/{pkg_path}/{wasm_output_name}.wasm") r#as="fetch" r#type="application/wasm" crossorigin=nonce.clone().unwrap_or_default()/>
|
||||
<link
|
||||
rel="preload"
|
||||
href=format!("/{pkg_path}/{wasm_output_name}.wasm")
|
||||
r#as="fetch"
|
||||
r#type="application/wasm"
|
||||
crossorigin=nonce.clone().unwrap_or_default()
|
||||
/>
|
||||
<script type="module" nonce=nonce>
|
||||
{format!("{script}({pkg_path:?}, {output_name:?}, {wasm_output_name:?})")}
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
(function (reload_port, protocol) {
|
||||
let host = window.location.hostname;
|
||||
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
|
||||
ws.onmessage = (ev) => {
|
||||
@@ -20,4 +19,3 @@ ws.onmessage = (ev) => {
|
||||
}
|
||||
};
|
||||
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
|
||||
})
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
use std::borrow::Cow;
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
renderer::dom::Dom,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
ssr::StreamBuilder,
|
||||
view::{add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
|
||||
ToTemplate,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct View<T>(T)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct View<T>
|
||||
where
|
||||
T: Sized;
|
||||
T: Sized,
|
||||
{
|
||||
inner: T,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl<T> View<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
self.inner
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn with_view_marker(
|
||||
#[allow(unused_mut)] // used in debug
|
||||
mut self,
|
||||
#[allow(unused_variables)] // used in debug
|
||||
view_marker: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.view_marker = Some(view_marker.into());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,33 +60,37 @@ where
|
||||
T: Sized + Render<Dom> + RenderHtml<Dom> + Send, //+ AddAnyAttr<Dom>,
|
||||
{
|
||||
fn into_view(self) -> View<Self> {
|
||||
View(self)
|
||||
View {
|
||||
inner: self,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView> Render<Dom> for View<T> {
|
||||
impl<T: Render<Rndr>, Rndr: Renderer> Render<Rndr> for View<T> {
|
||||
type State = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.0.build()
|
||||
self.inner.build()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.0.rebuild(state)
|
||||
self.inner.rebuild(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
||||
impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
|
||||
const MIN_LENGTH: usize = <T as RenderHtml<Dom>>::MIN_LENGTH;
|
||||
const MIN_LENGTH: usize = <T as RenderHtml<Rndr>>::MIN_LENGTH;
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.0.resolve().await
|
||||
self.inner.resolve().await
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.0.dry_resolve();
|
||||
self.inner.dry_resolve();
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
@@ -64,8 +100,20 @@ impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.0
|
||||
#[cfg(debug_assertions)]
|
||||
let vm = self.view_marker.to_owned();
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(vm) = vm.as_ref() {
|
||||
buf.push_str(&format!("<!--hot-reload|{vm}|open-->"));
|
||||
}
|
||||
|
||||
self.inner
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(vm) = vm.as_ref() {
|
||||
buf.push_str(&format!("<!--hot-reload|{vm}|close-->"));
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
@@ -77,35 +125,70 @@ impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.0.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
#[cfg(debug_assertions)]
|
||||
let vm = self.view_marker.to_owned();
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(vm) = vm.as_ref() {
|
||||
buf.push_sync(&format!("<!--hot-reload|{vm}|open-->"));
|
||||
}
|
||||
|
||||
self.inner.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
)
|
||||
);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(vm) = vm.as_ref() {
|
||||
buf.push_sync(&format!("<!--hot-reload|{vm}|close-->"));
|
||||
}
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Dom>,
|
||||
cursor: &Cursor<Rndr>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
self.0.hydrate::<FROM_SERVER>(cursor, position)
|
||||
self.inner.hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView> AddAnyAttr<Dom> for View<T> {
|
||||
type Output<SomeNewAttr: Attribute<Dom>> =
|
||||
<T as AddAnyAttr<Dom>>::Output<SomeNewAttr>;
|
||||
impl<T: ToTemplate> ToTemplate for View<T> {
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
position: &mut Position,
|
||||
) {
|
||||
T::to_template(buf, class, style, inner_html, position);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
||||
impl<T: AddAnyAttr<Rndr>, Rndr> AddAnyAttr<Rndr> for View<T>
|
||||
where
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = View<T::Output<SomeNewAttr>>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
self.0.add_any_attr(attr)
|
||||
let View {
|
||||
inner,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker,
|
||||
} = self;
|
||||
View {
|
||||
inner: inner.add_any_attr(attr),
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//#!rdeny(missing_docs)] // TODO restore
|
||||
#!rdeny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
//! # About Leptos
|
||||
//!
|
||||
@@ -98,25 +98,25 @@
|
||||
//! # A Simple Counter
|
||||
//!
|
||||
//! ```rust
|
||||
//! use leptos::*;
|
||||
//! use leptos::prelude::*;
|
||||
//!
|
||||
//! #[component]
|
||||
//! pub fn SimpleCounter( initial_value: i32) -> impl IntoView {
|
||||
//! pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
|
||||
//! // create a reactive signal with the initial value
|
||||
//! let (value, set_value) = create_signal( initial_value);
|
||||
//! let (value, set_value) = signal( initial_value);
|
||||
//!
|
||||
//! // create event handlers for our buttons
|
||||
//! // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
|
||||
//! let clear = move |_| set_value.set(0);
|
||||
//! let decrement = move |_| set_value.update(|value| *value -= 1);
|
||||
//! let increment = move |_| set_value.update(|value| *value += 1);
|
||||
//! let decrement = move |_| *set_value.write() -= 1;
|
||||
//! let increment = move |_| *set_value.write() += 1;
|
||||
//!
|
||||
//! view! {
|
||||
//!
|
||||
//! <div>
|
||||
//! <button on:click=clear>"Clear"</button>
|
||||
//! <button on:click=decrement>"-1"</button>
|
||||
//! <span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
//! <span>"Value: " {value} "!"</span>
|
||||
//! <button on:click=increment>"+1"</button>
|
||||
//! </div>
|
||||
//! }
|
||||
@@ -125,24 +125,23 @@
|
||||
//!
|
||||
//! Leptos is easy to use with [Trunk](https://trunkrs.dev/) (or with a simple wasm-bindgen setup):
|
||||
//! ```
|
||||
//! # use leptos::*;
|
||||
//! # if false { // can't run in doctests
|
||||
//! use leptos::{mount::mount_to_body, prelude::*};
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn SimpleCounter(initial_value: i32) -> impl IntoView {
|
||||
//! todo!()
|
||||
//! // ...
|
||||
//! # _ = initial_value;
|
||||
//! }
|
||||
//!
|
||||
//! pub fn main() {
|
||||
//! # if false { // can't run in doctest
|
||||
//! mount_to_body(|| view! { <SimpleCounter initial_value=3 /> })
|
||||
//! }
|
||||
//! # }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(auto_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(negative_impls))]
|
||||
|
||||
extern crate self as leptos;
|
||||
|
||||
@@ -296,10 +295,17 @@ pub mod spawn {
|
||||
}
|
||||
}
|
||||
|
||||
// these reexports are used in islands
|
||||
#[cfg(feature = "experimental-islands")]
|
||||
#[doc(hidden)]
|
||||
pub use wasm_bindgen; // used in islands
|
||||
pub use serde;
|
||||
#[cfg(feature = "experimental-islands")]
|
||||
#[doc(hidden)]
|
||||
pub use web_sys; // used in islands
|
||||
pub use serde_json;
|
||||
#[doc(hidden)]
|
||||
pub use wasm_bindgen;
|
||||
#[doc(hidden)]
|
||||
pub use web_sys;
|
||||
|
||||
/*mod additional_attributes;
|
||||
pub use additional_attributes::*;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::{logging, IntoView};
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::logging;
|
||||
use crate::IntoView;
|
||||
use any_spawner::Executor;
|
||||
use reactive_graph::owner::Owner;
|
||||
#[cfg(debug_assertions)]
|
||||
use std::cell::Cell;
|
||||
use std::marker::PhantomData;
|
||||
use tachys::{
|
||||
dom::body,
|
||||
@@ -27,6 +31,11 @@ where
|
||||
owner.forget();
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local! {
|
||||
static FIRST_CALL: Cell<bool> = const { Cell::new(true) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
/// Runs the provided closure and mounts the result to the provided element.
|
||||
pub fn hydrate_from<F, N>(
|
||||
@@ -45,15 +54,19 @@ where
|
||||
// already initialized, which is not an issue
|
||||
_ = Executor::init_wasm_bindgen();
|
||||
|
||||
if !cfg!(feature = "hydrate") {
|
||||
logging::warn!(
|
||||
"It seems like you're trying to use Leptos in hydration mode, but \
|
||||
the `hydrate` feature is not enabled on the `leptos` crate. Add \
|
||||
`features = [\"hydrate\"]` to your Cargo.toml for the crate to \
|
||||
work properly.\n\nNote that hydration and client-side rendering \
|
||||
now use separate functions from leptos::mount: you are calling a \
|
||||
hydration function."
|
||||
);
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if !cfg!(feature = "hydrate") && FIRST_CALL.get() {
|
||||
logging::warn!(
|
||||
"It seems like you're trying to use Leptos in hydration mode, \
|
||||
but the `hydrate` feature is not enabled on the `leptos` \
|
||||
crate. Add `features = [\"hydrate\"]` to your Cargo.toml for \
|
||||
the crate to work properly.\n\nNote that hydration and \
|
||||
client-side rendering now use separate functions from \
|
||||
leptos::mount: you are calling a hydration function."
|
||||
);
|
||||
}
|
||||
FIRST_CALL.set(false);
|
||||
}
|
||||
|
||||
// create a new reactive owner and use it as the root node to run the app
|
||||
@@ -100,16 +113,20 @@ where
|
||||
// already initialized, which is not an issue
|
||||
_ = Executor::init_wasm_bindgen();
|
||||
|
||||
if !cfg!(feature = "csr") {
|
||||
logging::warn!(
|
||||
"It seems like you're trying to use Leptos in client-side \
|
||||
rendering mode, but the `csr` feature is not enabled on the \
|
||||
`leptos` crate. Add `features = [\"csr\"]` to your Cargo.toml \
|
||||
for the crate to work properly.\n\nNote that hydration and \
|
||||
client-side rendering now use different functions from \
|
||||
leptos::mount. You are using a client-side rendering mount \
|
||||
function."
|
||||
);
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if !cfg!(feature = "csr") && FIRST_CALL.get() {
|
||||
logging::warn!(
|
||||
"It seems like you're trying to use Leptos in client-side \
|
||||
rendering mode, but the `csr` feature is not enabled on the \
|
||||
`leptos` crate. Add `features = [\"csr\"]` to your \
|
||||
Cargo.toml for the crate to work properly.\n\nNote that \
|
||||
hydration and client-side rendering now use different \
|
||||
functions from leptos::mount. You are using a client-side \
|
||||
rendering mount function."
|
||||
);
|
||||
}
|
||||
FIRST_CALL.set(false);
|
||||
}
|
||||
|
||||
// create a new reactive owner and use it as the root node to run the app
|
||||
@@ -166,7 +183,7 @@ where
|
||||
/// Hydrates any islands that are currently present on the page.
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn hydrate_islands() {
|
||||
use hydration_context::HydrateSharedContext;
|
||||
use hydration_context::{HydrateSharedContext, SharedContext};
|
||||
use std::sync::Arc;
|
||||
|
||||
// use wasm-bindgen-futures to drive the reactive system
|
||||
@@ -174,8 +191,13 @@ pub fn hydrate_islands() {
|
||||
// already initialized, which is not an issue
|
||||
_ = Executor::init_wasm_bindgen();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
FIRST_CALL.set(false);
|
||||
|
||||
// create a new reactive owner and use it as the root node to run the app
|
||||
let owner = Owner::new_root(Some(Arc::new(HydrateSharedContext::new())));
|
||||
let sc = HydrateSharedContext::new();
|
||||
sc.set_is_hydrating(false); // islands mode starts in "not hydrating"
|
||||
let owner = Owner::new_root(Some(Arc::new(sc)));
|
||||
owner.set();
|
||||
std::mem::forget(owner);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{children::TypedChildrenFn, mount, IntoView};
|
||||
use leptos_dom::helpers::document;
|
||||
use leptos_macro::component;
|
||||
use reactive_graph::{effect::Effect, owner::Owner};
|
||||
use reactive_graph::{effect::Effect, owner::Owner, untrack};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Renders components somewhere else in the DOM.
|
||||
@@ -62,12 +62,11 @@ where
|
||||
container.clone()
|
||||
};
|
||||
|
||||
// SendWrapper: this is only created in a single-threaded browser environment
|
||||
let _ = mount.append_child(&container);
|
||||
let handle = SendWrapper::new((
|
||||
mount::mount_to(render_root.unchecked_into(), {
|
||||
let children = Arc::clone(&children);
|
||||
move || children()
|
||||
move || untrack(|| children())
|
||||
}),
|
||||
mount.clone(),
|
||||
container,
|
||||
|
||||
@@ -9,7 +9,8 @@ use tachys::reactive_graph::OwnedView;
|
||||
/// This prevents issues related to “context shadowing.”
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::prelude::*;
|
||||
/// use leptos::{context::Provider, prelude::*};
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn App() -> impl IntoView {
|
||||
/// // each Provider will only provide the value to its children
|
||||
|
||||
@@ -31,6 +31,61 @@ use tachys::{
|
||||
};
|
||||
use throw_error::ErrorHookFuture;
|
||||
|
||||
/// If any [`Resource`](leptos_reactive::Resource) is read in the `children` of this
|
||||
/// component, it will show the `fallback` while they are loading. Once all are resolved,
|
||||
/// it will render the `children`.
|
||||
///
|
||||
/// Each time one of the resources is loading again, it will fall back. To keep the current
|
||||
/// children instead, use [Transition](crate::Transition).
|
||||
///
|
||||
/// Note that the `children` will be rendered initially (in order to capture the fact that
|
||||
/// those resources are read under the suspense), so you cannot assume that resources read
|
||||
/// synchronously have
|
||||
/// `Some` value in `children`. However, you can read resources asynchronously by using
|
||||
/// [Suspend](crate::prelude::Suspend).
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
/// # if false { // don't run in doctests
|
||||
/// async fn fetch_cats(how_many: u32) -> Vec<String> { vec![] }
|
||||
///
|
||||
/// let (cat_count, set_cat_count) = signal::<u32>(1);
|
||||
///
|
||||
/// let cats = Resource::new(move || cat_count.get(), |count| fetch_cats(count));
|
||||
///
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <Suspense fallback=move || view! { <p>"Loading (Suspense Fallback)..."</p> }>
|
||||
/// // you can access a resource synchronously
|
||||
/// {move || {
|
||||
/// cats.get().map(|data| {
|
||||
/// data
|
||||
/// .into_iter()
|
||||
/// .map(|src| {
|
||||
/// view! {
|
||||
/// <img src={src}/>
|
||||
/// }
|
||||
/// })
|
||||
/// .collect_view()
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// // or you can use `Suspend` to read resources asynchronously
|
||||
/// {move || Suspend::new(async move {
|
||||
/// cats.await
|
||||
/// .into_iter()
|
||||
/// .map(|src| {
|
||||
/// view! {
|
||||
/// <img src={src}/>
|
||||
/// }
|
||||
/// })
|
||||
/// .collect_view()
|
||||
/// })}
|
||||
/// </Suspense>
|
||||
/// </div>
|
||||
/// }
|
||||
/// # ;}
|
||||
/// ```
|
||||
#[component]
|
||||
pub fn Suspense<Chil>(
|
||||
#[prop(optional, into)] fallback: ViewFnOnce,
|
||||
@@ -387,3 +442,101 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that prevents [`Suspense`] from waiting for any resource reads that happen inside
|
||||
/// `Unsuspend`.
|
||||
pub struct Unsuspend<T>(Box<dyn FnOnce() -> T + Send>);
|
||||
|
||||
impl<T> Unsuspend<T> {
|
||||
/// Wraps the given function, such that it is not called until all resources are ready.
|
||||
pub fn new(fun: impl FnOnce() -> T + Send + 'static) -> Self {
|
||||
Self(Box::new(fun))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> Render<Rndr> for Unsuspend<T>
|
||||
where
|
||||
T: Render<Rndr>,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type State = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
(self.0)().build()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
(self.0)().rebuild(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> AddAnyAttr<Rndr> for Unsuspend<T>
|
||||
where
|
||||
T: AddAnyAttr<Rndr> + 'static,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> =
|
||||
Unsuspend<T::Output<SomeNewAttr::CloneableOwned>>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
let attr = attr.into_cloneable_owned();
|
||||
Unsuspend::new(move || (self.0)().add_any_attr(attr))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> RenderHtml<Rndr> for Unsuspend<T>
|
||||
where
|
||||
T: RenderHtml<Rndr> + 'static,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
(self.0)().to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
(self.0)().to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
(self.0)().hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,61 @@ use reactive_graph::{
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use tachys::reactive_graph::OwnedView;
|
||||
|
||||
/// TODO docs!
|
||||
/// If any [`Resource`](leptos_reactive::Resource) is read in the `children` of this
|
||||
/// component, it will show the `fallback` while they are loading. Once all are resolved,
|
||||
/// it will render the `children`.
|
||||
///
|
||||
/// Unlike [`Suspense`](crate::Suspense), this will not fall
|
||||
/// back to the `fallback` state if there are further changes after the initial load.
|
||||
///
|
||||
/// Note that the `children` will be rendered initially (in order to capture the fact that
|
||||
/// those resources are read under the suspense), so you cannot assume that resources read
|
||||
/// synchronously have
|
||||
/// `Some` value in `children`. However, you can read resources asynchronously by using
|
||||
/// [Suspend](crate::prelude::Suspend).
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
/// # if false { // don't run in doctests
|
||||
/// async fn fetch_cats(how_many: u32) -> Vec<String> { vec![] }
|
||||
///
|
||||
/// let (cat_count, set_cat_count) = signal::<u32>(1);
|
||||
///
|
||||
/// let cats = Resource::new(move || cat_count.get(), |count| fetch_cats(count));
|
||||
///
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <Transition fallback=move || view! { <p>"Loading (Suspense Fallback)..."</p> }>
|
||||
/// // you can access a resource synchronously
|
||||
/// {move || {
|
||||
/// cats.get().map(|data| {
|
||||
/// data
|
||||
/// .into_iter()
|
||||
/// .map(|src| {
|
||||
/// view! {
|
||||
/// <img src={src}/>
|
||||
/// }
|
||||
/// })
|
||||
/// .collect_view()
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// // or you can use `Suspend` to read resources asynchronously
|
||||
/// {move || Suspend::new(async move {
|
||||
/// cats.await
|
||||
/// .into_iter()
|
||||
/// .map(|src| {
|
||||
/// view! {
|
||||
/// <img src={src}/>
|
||||
/// }
|
||||
/// })
|
||||
/// .collect_view()
|
||||
/// })}
|
||||
/// </Transition>
|
||||
/// </div>
|
||||
/// }
|
||||
/// # ;}
|
||||
/// ```
|
||||
#[component]
|
||||
pub fn Transition<Chil>(
|
||||
/// Will be displayed while resources are pending. By default this is the empty view.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
use leptos::html::HtmlElement;
|
||||
|
||||
#[test]
|
||||
fn simple_ssr_test() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let runtime = create_runtime();
|
||||
let (value, set_value) = signal(0);
|
||||
let rendered = view! {
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
@@ -13,26 +13,13 @@ fn simple_ssr_test() {
|
||||
</div>
|
||||
};
|
||||
|
||||
if cfg!(all(feature = "experimental-islands", feature = "ssr")) {
|
||||
assert_eq!(
|
||||
rendered.into_view().to_html(),
|
||||
"<div><button>-1</button><span>Value: \
|
||||
0!</span><button>+1</button></div>"
|
||||
);
|
||||
} else {
|
||||
assert!(rendered.into_view().to_html().contains(
|
||||
"<div data-hk=\"0-0-0-1\"><button \
|
||||
data-hk=\"0-0-0-2\">-1</button><span data-hk=\"0-0-0-3\">Value: \
|
||||
<!--hk=0-0-0-4o|leptos-dyn-child-start-->0<!\
|
||||
--hk=0-0-0-4c|leptos-dyn-child-end-->!</span><button \
|
||||
data-hk=\"0-0-0-5\">+1</button></div>"
|
||||
));
|
||||
}
|
||||
|
||||
runtime.dispose();
|
||||
assert_eq!(
|
||||
rendered.to_html(),
|
||||
"<div><button>-1</button><span>Value: \
|
||||
<!>0<!>!</span><button>+1</button></div>"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn ssr_test_with_components() {
|
||||
use leptos::prelude::*;
|
||||
@@ -49,35 +36,21 @@ fn ssr_test_with_components() {
|
||||
}
|
||||
}
|
||||
|
||||
let runtime = create_runtime();
|
||||
let rendered = view! {
|
||||
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<div class="counters">
|
||||
<Counter initial_value=1/>
|
||||
<Counter initial_value=2/>
|
||||
</div>
|
||||
};
|
||||
|
||||
if cfg!(all(feature = "experimental-islands", feature = "ssr")) {
|
||||
assert_eq!(
|
||||
rendered.into_view().to_html(),
|
||||
"<div class=\"counters\"><div><button>-1</button><span>Value: \
|
||||
1!</span><button>+1</button></div><div><button>-1</\
|
||||
button><span>Value: 2!</span><button>+1</button></div></div>"
|
||||
);
|
||||
} else {
|
||||
assert!(rendered.into_view().to_html().contains(
|
||||
"<div data-hk=\"0-0-0-3\"><button \
|
||||
data-hk=\"0-0-0-4\">-1</button><span data-hk=\"0-0-0-5\">Value: \
|
||||
<!--hk=0-0-0-6o|leptos-dyn-child-start-->1<!\
|
||||
--hk=0-0-0-6c|leptos-dyn-child-end-->!</span><button \
|
||||
data-hk=\"0-0-0-7\">+1</button></div>"
|
||||
));
|
||||
}
|
||||
runtime.dispose();
|
||||
assert_eq!(
|
||||
rendered.to_html(),
|
||||
"<div class=\"counters\"><div><button>-1</button><span>Value: \
|
||||
<!>1<!>!</span><button>+1</button></div><div><button>-1</\
|
||||
button><span>Value: <!>2<!>!</span><button>+1</button></div></div>"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn ssr_test_with_snake_case_components() {
|
||||
use leptos::prelude::*;
|
||||
@@ -86,7 +59,6 @@ fn ssr_test_with_snake_case_components() {
|
||||
fn snake_case_counter(initial_value: i32) -> impl IntoView {
|
||||
let (value, set_value) = signal(initial_value);
|
||||
view! {
|
||||
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
@@ -94,111 +66,65 @@ fn ssr_test_with_snake_case_components() {
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
let runtime = create_runtime();
|
||||
let rendered = view! {
|
||||
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<div class="counters">
|
||||
<SnakeCaseCounter initial_value=1/>
|
||||
<SnakeCaseCounter initial_value=2/>
|
||||
</div>
|
||||
};
|
||||
|
||||
if cfg!(all(feature = "experimental-islands", feature = "ssr")) {
|
||||
assert_eq!(
|
||||
rendered.into_view().to_html(),
|
||||
"<div class=\"counters\"><div><button>-1</button><span>Value: \
|
||||
1!</span><button>+1</button></div><div><button>-1</\
|
||||
button><span>Value: 2!</span><button>+1</button></div></div>"
|
||||
);
|
||||
} else {
|
||||
assert!(rendered.into_view().to_html().contains(
|
||||
"<div data-hk=\"0-0-0-3\"><button \
|
||||
data-hk=\"0-0-0-4\">-1</button><span data-hk=\"0-0-0-5\">Value: \
|
||||
<!--hk=0-0-0-6o|leptos-dyn-child-start-->1<!\
|
||||
--hk=0-0-0-6c|leptos-dyn-child-end-->!</span><button \
|
||||
data-hk=\"0-0-0-7\">+1</button></div>"
|
||||
));
|
||||
}
|
||||
|
||||
runtime.dispose();
|
||||
assert_eq!(
|
||||
rendered.to_html(),
|
||||
"<div class=\"counters\"><div><button>-1</button><span>Value: \
|
||||
<!>1<!>!</span><button>+1</button></div><div><button>-1</\
|
||||
button><span>Value: <!>2<!>!</span><button>+1</button></div></div>"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn test_classes() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let runtime = create_runtime();
|
||||
let (value, _set_value) = signal(5);
|
||||
let rendered = view! {
|
||||
|
||||
<div class="my big" class:a={move || value.get() > 10} class:red=true class:car={move || value.get() > 1}></div>
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
<div
|
||||
class="my big"
|
||||
class:a=move || { value.get() > 10 }
|
||||
class:red=true
|
||||
class:car=move || { value.get() > 1 }
|
||||
></div>
|
||||
};
|
||||
|
||||
if cfg!(all(feature = "experimental-islands", feature = "ssr")) {
|
||||
assert_eq!(
|
||||
rendered.into_view().to_html(),
|
||||
"<div class=\"my big red car\"></div>"
|
||||
);
|
||||
} else {
|
||||
assert!(rendered.into_view().to_html().contains(
|
||||
"<div data-hk=\"0-0-0-1\" class=\"my big red car\"></div>"
|
||||
));
|
||||
}
|
||||
runtime.dispose();
|
||||
assert_eq!(rendered.to_html(), "<div class=\"my big red car\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn ssr_with_styles() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let runtime = create_runtime();
|
||||
let (_, set_value) = signal(0);
|
||||
let styles = "myclass";
|
||||
let rendered = view! {
|
||||
class = styles,
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { class=styles,
|
||||
<div>
|
||||
<button class="btn" on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<button class="btn" on:click=move |_| set_value.update(|value| *value -= 1)>
|
||||
"-1"
|
||||
</button>
|
||||
</div>
|
||||
};
|
||||
|
||||
if cfg!(all(feature = "experimental-islands", feature = "ssr")) {
|
||||
assert_eq!(
|
||||
rendered.into_view().to_html(),
|
||||
"<div class=\" myclass\"><button class=\"btn \
|
||||
myclass\">-1</button></div>"
|
||||
);
|
||||
} else {
|
||||
assert!(rendered.into_view().to_html().contains(
|
||||
"<div data-hk=\"0-0-0-1\" class=\" myclass\"><button \
|
||||
data-hk=\"0-0-0-2\" class=\"btn myclass\">-1</button></div>"
|
||||
));
|
||||
}
|
||||
runtime.dispose();
|
||||
assert_eq!(
|
||||
rendered.to_html(),
|
||||
"<div class=\"myclass\"><button class=\"btn \
|
||||
myclass\">-1</button></div>"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn ssr_option() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let runtime = create_runtime();
|
||||
let (_, _) = signal(0);
|
||||
let rendered = view! {
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { <option></option> };
|
||||
|
||||
<option/>
|
||||
};
|
||||
|
||||
if cfg!(all(feature = "experimental-islands", feature = "ssr")) {
|
||||
assert_eq!(rendered.into_view().to_html(), "<option></option>");
|
||||
} else {
|
||||
assert!(rendered
|
||||
.into_view()
|
||||
.to_html()
|
||||
.contains("<option data-hk=\"0-0-0-1\"></option>"));
|
||||
}
|
||||
|
||||
runtime.dispose();
|
||||
assert_eq!(rendered.to_html(), "<option></option>");
|
||||
}
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
[package]
|
||||
name = "leptos_config"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Configuration for the Leptos web framework."
|
||||
readme = "../README.md"
|
||||
version = { workspace = true }
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
config = { version = "0.14", default-features = false, features = ["toml", "convert-case"] }
|
||||
regex = "1.7.0"
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
thiserror = "1.0.38"
|
||||
typed-builder = "0.18"
|
||||
config = { version = "0.14.0", default-features = false, features = [
|
||||
"toml",
|
||||
"convert-case",
|
||||
] }
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
typed-builder = "0.19.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
tempfile = "3"
|
||||
tokio = { version = "1.39", features = ["rt", "macros"] }
|
||||
tempfile = "3.12"
|
||||
temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -1,40 +1,31 @@
|
||||
[package]
|
||||
name = "leptos_dom"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "DOM operations for the Leptos web framework."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tachys = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
hydration_context = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
base64 = { version = "0.21", optional = true }
|
||||
getrandom = { version = "0.2", optional = true }
|
||||
js-sys = "0.3"
|
||||
rand = { version = "0.8", optional = true }
|
||||
send_wrapper = "0.6"
|
||||
tracing = "0.1"
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3.69"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = "0.1.40"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
version = "0.3.70"
|
||||
features = ["Location"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
nightly = ["reactive_graph/nightly"]
|
||||
# TODO implement nonces
|
||||
nonce = ["dep:base64", "dep:getrandom", "dep:rand"]
|
||||
experimental-islands = []
|
||||
trace-component-props = ["tracing"]
|
||||
tracing = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -9,11 +9,11 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
leptos = { path = "../../../leptos", features = ["nightly"] }
|
||||
actix-web = { version = "4", optional = true }
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
gloo = { version = "0.8", optional = true }
|
||||
actix-files = { version = "0.6.0", optional = true }
|
||||
wasm-bindgen = { version = "0.2.0", optional = true }
|
||||
gloo = { version = "0.11.0", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3"
|
||||
futures = "0.3.0"
|
||||
|
||||
[features]
|
||||
default = ["ssr"]
|
||||
|
||||
@@ -4,11 +4,11 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
gloo = { version = "0.8", features = ["futures"] }
|
||||
console_error_panic_hook = "0.1.0"
|
||||
gloo = { version = "0.11.0", features = ["futures"] }
|
||||
leptos = { path = "../../../leptos", features = ["nightly", "csr", "tracing"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tracing-subscriber-wasm = "0.1"
|
||||
tracing = "0.1.0"
|
||||
tracing-subscriber = "0.3.0"
|
||||
tracing-subscriber-wasm = "0.1.0"
|
||||
|
||||
[workspace]
|
||||
|
||||
@@ -5,10 +5,9 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_log = "1.0"
|
||||
log = "0.4.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
mod dyn_child;
|
||||
mod each;
|
||||
mod errors;
|
||||
mod fragment;
|
||||
mod unit;
|
||||
|
||||
use crate::{
|
||||
hydration::{HydrationCtx, HydrationKey},
|
||||
Comment, IntoView, View,
|
||||
};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use crate::{mount_child, prepare_to_move, MountKind, Mountable};
|
||||
pub use dyn_child::*;
|
||||
pub use each::*;
|
||||
pub use errors::*;
|
||||
pub use fragment::*;
|
||||
use leptos_reactive::{untrack_with_diagnostics, Oco};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::fmt;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::rc::Rc;
|
||||
pub use unit::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
/// The core foundational leptos components.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum CoreComponent {
|
||||
/// The [Unit] component.
|
||||
Unit(UnitRepr),
|
||||
/// The [DynChild] component.
|
||||
DynChild(DynChildRepr),
|
||||
/// The [Each] component.
|
||||
Each(EachRepr),
|
||||
}
|
||||
|
||||
impl Default for CoreComponent {
|
||||
fn default() -> Self {
|
||||
Self::Unit(UnitRepr::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CoreComponent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Unit(u) => u.fmt(f),
|
||||
Self::DynChild(dc) => dc.fmt(f),
|
||||
Self::Each(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom leptos component.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ComponentRepr {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) document_fragment: web_sys::DocumentFragment,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
mounted: Rc<OnceCell<()>>,
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
pub(crate) name: Oco<'static, str>,
|
||||
#[cfg(debug_assertions)]
|
||||
_opening: Comment,
|
||||
/// The children of the component.
|
||||
pub children: Vec<View>,
|
||||
closing: Comment,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
pub(crate) id: Option<HydrationKey>,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) view_marker: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ComponentRepr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use fmt::Write;
|
||||
|
||||
if self.children.is_empty() {
|
||||
#[cfg(debug_assertions)]
|
||||
return write!(f, "<{} />", self.name);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
return f.write_str("<Component />");
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
writeln!(f, "<{}>", self.name)?;
|
||||
#[cfg(not(debug_assertions))]
|
||||
f.write_str("<Component>")?;
|
||||
|
||||
let mut pad_adapter = pad_adapter::PadAdapter::new(f);
|
||||
|
||||
for child in &self.children {
|
||||
writeln!(pad_adapter, "{child:#?}")?;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
write!(f, "</{}>", self.name)?;
|
||||
#[cfg(not(debug_assertions))]
|
||||
f.write_str("</Component>")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
impl Mountable for ComponentRepr {
|
||||
fn get_mountable_node(&self) -> web_sys::Node {
|
||||
if self.mounted.get().is_none() {
|
||||
self.mounted.set(()).unwrap();
|
||||
|
||||
self.document_fragment
|
||||
.unchecked_ref::<web_sys::Node>()
|
||||
.to_owned()
|
||||
}
|
||||
// We need to prepare all children to move
|
||||
else {
|
||||
let opening = self.get_opening_node();
|
||||
|
||||
prepare_to_move(
|
||||
&self.document_fragment,
|
||||
&opening,
|
||||
&self.closing.node,
|
||||
);
|
||||
|
||||
self.document_fragment.clone().unchecked_into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_opening_node(&self) -> web_sys::Node {
|
||||
#[cfg(debug_assertions)]
|
||||
return self._opening.node.clone();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
return if let Some(child) = self.children.get(0) {
|
||||
child.get_opening_node()
|
||||
} else {
|
||||
self.closing.node.clone()
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_closing_node(&self) -> web_sys::Node {
|
||||
self.closing.node.clone()
|
||||
}
|
||||
}
|
||||
impl From<ComponentRepr> for View {
|
||||
fn from(value: ComponentRepr) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
for child in &value.children {
|
||||
mount_child(MountKind::Before(&value.closing.node), child);
|
||||
}
|
||||
}
|
||||
|
||||
View::Component(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoView for ComponentRepr {
|
||||
#[cfg_attr(any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "<Component />", skip_all, fields(name = %self.name)))]
|
||||
fn into_view(self) -> View {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentRepr {
|
||||
/// Creates a new [`Component`].
|
||||
#[inline(always)]
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
Self::new_with_id_concrete(name.into(), HydrationCtx::id())
|
||||
}
|
||||
|
||||
/// Creates a new [`Component`] with the given hydration ID.
|
||||
#[inline(always)]
|
||||
pub fn new_with_id(
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
id: Option<HydrationKey>,
|
||||
) -> Self {
|
||||
Self::new_with_id_concrete(name.into(), id)
|
||||
}
|
||||
|
||||
fn new_with_id_concrete(
|
||||
name: Oco<'static, str>,
|
||||
id: Option<HydrationKey>,
|
||||
) -> Self {
|
||||
let markers = (
|
||||
Comment::new(format!("</{name}>"), &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(format!("<{name}>"), &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let document_fragment = {
|
||||
let fragment = crate::document().create_document_fragment();
|
||||
|
||||
// Insert the comments into the document fragment
|
||||
// so they can serve as our references when inserting
|
||||
// future nodes
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
#[cfg(debug_assertions)]
|
||||
fragment
|
||||
.append_with_node_2(&markers.1.node, &markers.0.node)
|
||||
.expect("append to not err");
|
||||
#[cfg(not(debug_assertions))]
|
||||
fragment
|
||||
.append_with_node_1(&markers.0.node)
|
||||
.expect("append to not err");
|
||||
}
|
||||
|
||||
fragment
|
||||
};
|
||||
|
||||
Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
document_fragment,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
mounted: Default::default(),
|
||||
#[cfg(debug_assertions)]
|
||||
_opening: markers.1,
|
||||
closing: markers.0,
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
name,
|
||||
children: Vec::with_capacity(1),
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
id,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
/// Returns the name of the component.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
/// A user-defined `leptos` component.
|
||||
pub struct Component<F, V>
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
V: IntoView,
|
||||
{
|
||||
id: Option<HydrationKey>,
|
||||
name: Oco<'static, str>,
|
||||
children_fn: F,
|
||||
}
|
||||
|
||||
impl<F, V> Component<F, V>
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
V: IntoView,
|
||||
{
|
||||
/// Creates a new component.
|
||||
pub fn new(name: impl Into<Oco<'static, str>>, f: F) -> Self {
|
||||
Self {
|
||||
id: HydrationCtx::id(),
|
||||
name: name.into(),
|
||||
children_fn: f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, V> IntoView for Component<F, V>
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
V: IntoView,
|
||||
{
|
||||
#[track_caller]
|
||||
fn into_view(self) -> View {
|
||||
let Self {
|
||||
id,
|
||||
name,
|
||||
children_fn,
|
||||
} = self;
|
||||
|
||||
let mut repr = ComponentRepr::new_with_id(name, id);
|
||||
|
||||
// disposed automatically when the parent scope is disposed
|
||||
let child = untrack_with_diagnostics(|| children_fn().into_view());
|
||||
|
||||
repr.children.push(child);
|
||||
|
||||
repr.into_view()
|
||||
}
|
||||
}
|
||||
@@ -1,452 +0,0 @@
|
||||
use crate::{
|
||||
hydration::{HydrationCtx, HydrationKey},
|
||||
Comment, IntoView, View,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{cell::RefCell, fmt, ops::Deref, rc::Rc};
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use crate::{mount_child, prepare_to_move, unmount_child, MountKind, Mountable, Text};
|
||||
use leptos_reactive::create_render_effect;
|
||||
use wasm_bindgen::JsCast;
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal representation of the [`DynChild`] core-component.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct DynChildRepr {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
document_fragment: web_sys::DocumentFragment,
|
||||
#[cfg(debug_assertions)]
|
||||
opening: Comment,
|
||||
pub(crate) child: Rc<RefCell<Box<Option<View>>>>,
|
||||
closing: Comment,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
pub(crate) id: Option<HydrationKey>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DynChildRepr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use fmt::Write;
|
||||
|
||||
f.write_str("<DynChild>\n")?;
|
||||
|
||||
let mut pad_adapter = pad_adapter::PadAdapter::new(f);
|
||||
|
||||
writeln!(
|
||||
pad_adapter,
|
||||
"{:#?}",
|
||||
self.child.borrow().deref().deref().as_ref().unwrap()
|
||||
)?;
|
||||
|
||||
f.write_str("</DynChild>")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
impl Mountable for DynChildRepr {
|
||||
fn get_mountable_node(&self) -> web_sys::Node {
|
||||
if self.document_fragment.child_nodes().length() != 0 {
|
||||
self.document_fragment.clone().unchecked_into()
|
||||
} else {
|
||||
let opening = self.get_opening_node();
|
||||
|
||||
prepare_to_move(
|
||||
&self.document_fragment,
|
||||
&opening,
|
||||
&self.closing.node,
|
||||
);
|
||||
|
||||
self.document_fragment.clone().unchecked_into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_opening_node(&self) -> web_sys::Node {
|
||||
#[cfg(debug_assertions)]
|
||||
return self.opening.node.clone();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
return self
|
||||
.child
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_opening_node();
|
||||
}
|
||||
|
||||
fn get_closing_node(&self) -> web_sys::Node {
|
||||
self.closing.node.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DynChildRepr {
|
||||
fn new_with_id(id: Option<HydrationKey>) -> Self {
|
||||
let markers = (
|
||||
Comment::new("</DynChild>", &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new("<DynChild>", &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let document_fragment = {
|
||||
let fragment = crate::document().create_document_fragment();
|
||||
|
||||
// Insert the comments into the document fragment
|
||||
// so they can serve as our references when inserting
|
||||
// future nodes
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
#[cfg(debug_assertions)]
|
||||
fragment
|
||||
.append_with_node_2(&markers.1.node, &markers.0.node)
|
||||
.unwrap();
|
||||
#[cfg(not(debug_assertions))]
|
||||
fragment.append_with_node_1(&markers.0.node).unwrap();
|
||||
}
|
||||
|
||||
fragment
|
||||
};
|
||||
|
||||
Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
document_fragment,
|
||||
#[cfg(debug_assertions)]
|
||||
opening: markers.1,
|
||||
child: Default::default(),
|
||||
closing: markers.0,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents any [`View`] that can change over time.
|
||||
pub struct DynChild<CF, N>
|
||||
where
|
||||
CF: Fn() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
id: Option<HydrationKey>,
|
||||
child_fn: CF,
|
||||
}
|
||||
|
||||
impl<CF, N> DynChild<CF, N>
|
||||
where
|
||||
CF: Fn() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
/// Creates a new dynamic child which will re-render whenever it's
|
||||
/// signal dependencies change.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn new(child_fn: CF) -> Self {
|
||||
Self::new_with_id(HydrationCtx::id(), child_fn)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub const fn new_with_id(id: Option<HydrationKey>, child_fn: CF) -> Self {
|
||||
Self { id, child_fn }
|
||||
}
|
||||
}
|
||||
|
||||
impl<CF, N> IntoView for DynChild<CF, N>
|
||||
where
|
||||
CF: Fn() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "trace", name = "<DynChild />", skip_all)
|
||||
)]
|
||||
#[inline]
|
||||
fn into_view(self) -> View {
|
||||
// concrete inner function
|
||||
#[inline(never)]
|
||||
fn create_dyn_view(
|
||||
component: DynChildRepr,
|
||||
child_fn: Box<dyn Fn() -> View>,
|
||||
) -> DynChildRepr {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let closing = component.closing.node.clone();
|
||||
|
||||
let child = component.child.clone();
|
||||
|
||||
#[cfg(all(
|
||||
debug_assertions,
|
||||
target_arch = "wasm32",
|
||||
feature = "web"
|
||||
))]
|
||||
let span = tracing::Span::current();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
create_render_effect(
|
||||
move |prev_run: Option<Option<web_sys::Node>>| {
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = span.enter();
|
||||
|
||||
let new_child = child_fn().into_view();
|
||||
|
||||
let mut child_borrow = child.borrow_mut();
|
||||
|
||||
// Is this at least the second time we are loading a child?
|
||||
if let Some(prev_t) = prev_run {
|
||||
let child = child_borrow.take().unwrap();
|
||||
|
||||
// We need to know if our child wasn't moved elsewhere.
|
||||
// If it was, `DynChild` no longer "owns" that child, and
|
||||
// is therefore no longer sound to unmount it from the DOM
|
||||
// or to reuse it in the case of a text node
|
||||
|
||||
// TODO check does this still detect moves correctly?
|
||||
let was_child_moved = prev_t.is_none()
|
||||
&& child
|
||||
.get_closing_node()
|
||||
.next_non_view_marker_sibling()
|
||||
.as_ref()
|
||||
!= Some(&closing);
|
||||
|
||||
// If the previous child was a text node, we would like to
|
||||
// make use of it again if our current child is also a text
|
||||
// node
|
||||
let ret = if let Some(prev_t) = prev_t {
|
||||
// Here, our child is also a text node
|
||||
|
||||
// nb: the match/ownership gymnastics here
|
||||
// are so that, if we can reuse the text node,
|
||||
// we can take ownership of new_t so we don't clone
|
||||
// the contents, which in O(n) on the length of the text
|
||||
if matches!(new_child, View::Text(_)) {
|
||||
if !was_child_moved && child != new_child {
|
||||
let mut new_t = match new_child {
|
||||
View::Text(t) => t,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
prev_t
|
||||
.unchecked_ref::<web_sys::Text>()
|
||||
.set_data(&new_t.content);
|
||||
|
||||
// replace new_t's text node with the prev node
|
||||
// see discussion: https://github.com/leptos-rs/leptos/pull/1472
|
||||
new_t.node = prev_t.clone();
|
||||
|
||||
let new_child = View::Text(new_t);
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
Some(prev_t)
|
||||
} else {
|
||||
let new_t = new_child.as_text().unwrap();
|
||||
mount_child(
|
||||
MountKind::Before(&closing),
|
||||
&new_child,
|
||||
);
|
||||
|
||||
**child_borrow = Some(new_child.clone());
|
||||
|
||||
Some(new_t.node.clone())
|
||||
}
|
||||
}
|
||||
// Child is not a text node, so we can remove the previous
|
||||
// text node
|
||||
else {
|
||||
if !was_child_moved && child != new_child {
|
||||
// Remove the text
|
||||
closing
|
||||
.previous_non_view_marker_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.remove();
|
||||
}
|
||||
|
||||
// Mount the new child, and we're done
|
||||
mount_child(
|
||||
MountKind::Before(&closing),
|
||||
&new_child,
|
||||
);
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
// Otherwise, the new child can still be a text node,
|
||||
// but we know the previous child was not, so no special
|
||||
// treatment here
|
||||
else {
|
||||
// Technically, I think this check shouldn't be necessary, but
|
||||
// I can imagine some edge case that the child changes while
|
||||
// hydration is ongoing
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
let same_child = child == new_child;
|
||||
if !was_child_moved && !same_child {
|
||||
// Remove the child
|
||||
let start = child.get_opening_node();
|
||||
let end = &closing;
|
||||
|
||||
match child {
|
||||
View::CoreComponent(
|
||||
crate::CoreComponent::DynChild(
|
||||
child,
|
||||
),
|
||||
) => {
|
||||
let start =
|
||||
child.get_opening_node();
|
||||
let end = child.closing.node;
|
||||
prepare_to_move(
|
||||
&child.document_fragment,
|
||||
&start,
|
||||
&end,
|
||||
);
|
||||
}
|
||||
View::Component(child) => {
|
||||
let start =
|
||||
child.get_opening_node();
|
||||
let end = child.closing.node;
|
||||
prepare_to_move(
|
||||
&child.document_fragment,
|
||||
&start,
|
||||
&end,
|
||||
);
|
||||
}
|
||||
_ => unmount_child(&start, end),
|
||||
}
|
||||
}
|
||||
|
||||
// Mount the new child
|
||||
// If it's the same child, don't re-mount
|
||||
if !same_child {
|
||||
mount_child(
|
||||
MountKind::Before(&closing),
|
||||
&new_child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We want to reuse text nodes, so hold onto it if
|
||||
// our child is one
|
||||
let t =
|
||||
new_child.get_text().map(|t| t.node.clone());
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
t
|
||||
};
|
||||
|
||||
ret
|
||||
}
|
||||
// Otherwise, we know for sure this is our first time
|
||||
else {
|
||||
// If it's a text node, we want to use the old text node
|
||||
// as the text node for the DynChild, rather than the new
|
||||
// text node being created during hydration
|
||||
let new_child = if HydrationCtx::is_hydrating()
|
||||
&& new_child.get_text().is_some()
|
||||
{
|
||||
let t = closing
|
||||
.previous_non_view_marker_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Text>();
|
||||
|
||||
let new_child = match new_child {
|
||||
View::Text(text) => text,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
t.set_data(&new_child.content);
|
||||
View::Text(Text {
|
||||
node: t.unchecked_into(),
|
||||
content: new_child.content,
|
||||
})
|
||||
} else {
|
||||
new_child
|
||||
};
|
||||
|
||||
// If we are not hydrating, we simply mount the child
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
mount_child(
|
||||
MountKind::Before(&closing),
|
||||
&new_child,
|
||||
);
|
||||
}
|
||||
|
||||
// We want to update text nodes, rather than replace them, so
|
||||
// make sure to hold onto the text node
|
||||
let t = new_child.get_text().map(|t| t.node.clone());
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
t
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
{
|
||||
let new_child = child_fn().into_view();
|
||||
|
||||
**child.borrow_mut() = Some(new_child);
|
||||
}
|
||||
|
||||
component
|
||||
}
|
||||
|
||||
// monomorphized outer function
|
||||
let Self { id, child_fn } = self;
|
||||
|
||||
let component = DynChildRepr::new_with_id(id);
|
||||
let component = create_dyn_view(
|
||||
component,
|
||||
Box::new(move || child_fn().into_view()),
|
||||
);
|
||||
|
||||
View::CoreComponent(crate::CoreComponent::DynChild(component))
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use web_sys::Node;
|
||||
|
||||
pub(crate) trait NonViewMarkerSibling {
|
||||
fn next_non_view_marker_sibling(&self) -> Option<Node>;
|
||||
|
||||
fn previous_non_view_marker_sibling(&self) -> Option<Node>;
|
||||
}
|
||||
|
||||
impl NonViewMarkerSibling for web_sys::Node {
|
||||
#[cfg_attr(not(debug_assertions), inline(always))]
|
||||
fn next_non_view_marker_sibling(&self) -> Option<Node> {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
self.next_sibling().and_then(|node| {
|
||||
if node.text_content().unwrap_or_default().trim().starts_with("leptos-view") {
|
||||
node.next_sibling()
|
||||
} else {
|
||||
Some(node)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.next_sibling()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(debug_assertions), inline(always))]
|
||||
fn previous_non_view_marker_sibling(&self) -> Option<Node> {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
self.previous_sibling().and_then(|node| {
|
||||
if node.text_content().unwrap_or_default().trim().starts_with("leptos-view") {
|
||||
node.previous_sibling()
|
||||
} else {
|
||||
Some(node)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.previous_sibling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,166 +0,0 @@
|
||||
use crate::{HydrationCtx, IntoView};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_reactive::{signal_prelude::*, use_context};
|
||||
use server_fn::error::Error;
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
/// A struct to hold all the possible errors that could be provided by child Views
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct Errors(HashMap<ErrorKey, Error>);
|
||||
|
||||
/// A unique key for an error that occurs at a particular location in the user interface.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ErrorKey(Cow<'static, str>);
|
||||
|
||||
impl<T> From<T> for ErrorKey
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(key: T) -> ErrorKey {
|
||||
ErrorKey(key.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Errors {
|
||||
type Item = (ErrorKey, Error);
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter(self.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// An owning iterator over all the errors contained in the [`Errors`] struct.
|
||||
#[repr(transparent)]
|
||||
pub struct IntoIter(std::collections::hash_map::IntoIter<ErrorKey, Error>);
|
||||
|
||||
impl Iterator for IntoIter {
|
||||
type Item = (ErrorKey, Error);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(
|
||||
&mut self,
|
||||
) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all the errors contained in the [`Errors`] struct.
|
||||
#[repr(transparent)]
|
||||
pub struct Iter<'a>(std::collections::hash_map::Iter<'a, ErrorKey, Error>);
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a ErrorKey, &'a Error);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(
|
||||
&mut self,
|
||||
) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> IntoView for Result<T, E>
|
||||
where
|
||||
T: IntoView + 'static,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn into_view(self) -> crate::View {
|
||||
let id = ErrorKey(
|
||||
HydrationCtx::peek()
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
);
|
||||
let errors = use_context::<RwSignal<Errors>>();
|
||||
match self {
|
||||
Ok(stuff) => {
|
||||
if let Some(errors) = errors {
|
||||
errors.update(|errors| {
|
||||
errors.0.remove(&id);
|
||||
});
|
||||
}
|
||||
stuff.into_view()
|
||||
}
|
||||
Err(error) => {
|
||||
let error = error.into();
|
||||
match errors {
|
||||
Some(errors) => {
|
||||
errors.update({
|
||||
#[cfg(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "web"
|
||||
))]
|
||||
let id = id.clone();
|
||||
move |errors: &mut Errors| errors.insert(id, error)
|
||||
});
|
||||
|
||||
// remove the error from the list if this drops,
|
||||
// i.e., if it's in a DynChild that switches from Err to Ok
|
||||
// Only can run on the client, will panic on the server
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use leptos_reactive::{on_cleanup, queue_microtask};
|
||||
on_cleanup(move || {
|
||||
queue_microtask(move || {
|
||||
errors.update(|errors: &mut Errors| {
|
||||
errors.remove(&id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
#[cfg(debug_assertions)]
|
||||
warn!(
|
||||
"No ErrorBoundary components found! Returning \
|
||||
errors will not be handled and will silently \
|
||||
disappear"
|
||||
);
|
||||
}
|
||||
}
|
||||
().into_view()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Errors {
|
||||
/// Returns `true` if there are no errors.
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Add an error to Errors that will be processed by `<ErrorBoundary/>`
|
||||
pub fn insert<E>(&mut self, key: ErrorKey, error: E)
|
||||
where
|
||||
E: Into<Error>,
|
||||
{
|
||||
self.0.insert(key, error.into());
|
||||
}
|
||||
|
||||
/// Add an error with the default key for errors outside the reactive system
|
||||
pub fn insert_with_default_key<E>(&mut self, error: E)
|
||||
where
|
||||
E: Into<Error>,
|
||||
{
|
||||
self.0.insert(Default::default(), error.into());
|
||||
}
|
||||
|
||||
/// Remove an error to Errors that will be processed by `<ErrorBoundary/>`
|
||||
pub fn remove(&mut self, key: &ErrorKey) -> Option<Error> {
|
||||
self.0.remove(key)
|
||||
}
|
||||
|
||||
/// An iterator over all the errors, in arbitrary order.
|
||||
#[inline(always)]
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter(self.0.iter())
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
use crate::{
|
||||
hydration::HydrationKey, ComponentRepr, HydrationCtx, IntoView, View,
|
||||
};
|
||||
|
||||
/// Trait for converting any iterable into a [`Fragment`].
|
||||
pub trait IntoFragment {
|
||||
/// Consumes this type, returning [`Fragment`].
|
||||
fn into_fragment(self) -> Fragment;
|
||||
}
|
||||
|
||||
impl<I, V> IntoFragment for I
|
||||
where
|
||||
I: IntoIterator<Item = V>,
|
||||
V: IntoView,
|
||||
{
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
)]
|
||||
fn into_fragment(self) -> Fragment {
|
||||
self.into_iter().map(|v| v.into_view()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a group of [`views`](View).
|
||||
#[must_use = "You are creating a Fragment but not using it. An unused view can \
|
||||
cause your view to be rendered as () unexpectedly, and it can \
|
||||
also cause issues with client-side hydration."]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Fragment {
|
||||
id: Option<HydrationKey>,
|
||||
/// The nodes contained in the fragment.
|
||||
pub nodes: Vec<View>,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) view_marker: Option<String>,
|
||||
}
|
||||
|
||||
impl FromIterator<View> for Fragment {
|
||||
fn from_iter<T: IntoIterator<Item = View>>(iter: T) -> Self {
|
||||
Fragment::new(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<View> for Fragment {
|
||||
fn from(view: View) -> Self {
|
||||
Fragment::new(vec![view])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fragment> for View {
|
||||
fn from(value: Fragment) -> Self {
|
||||
let mut frag = ComponentRepr::new_with_id("", value.id);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
frag.view_marker = value.view_marker;
|
||||
}
|
||||
|
||||
frag.children = value.nodes;
|
||||
|
||||
frag.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fragment {
|
||||
/// Creates a new [`Fragment`] from a [`Vec<Node>`].
|
||||
#[inline(always)]
|
||||
pub fn new(nodes: Vec<View>) -> Self {
|
||||
Self::new_with_id(HydrationCtx::id(), nodes)
|
||||
}
|
||||
|
||||
/// Creates a new [`Fragment`] from a function that returns [`Vec<Node>`].
|
||||
#[inline(always)]
|
||||
pub fn lazy(nodes: impl FnOnce() -> Vec<View>) -> Self {
|
||||
Self::new_with_id(HydrationCtx::id(), nodes())
|
||||
}
|
||||
|
||||
/// Creates a new [`Fragment`] with the given hydration ID from a [`Vec<Node>`].
|
||||
#[inline(always)]
|
||||
pub const fn new_with_id(
|
||||
id: Option<HydrationKey>,
|
||||
nodes: Vec<View>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
nodes,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives access to the [`View`] children contained within the fragment.
|
||||
#[inline(always)]
|
||||
pub fn as_children(&self) -> &[View] {
|
||||
&self.nodes
|
||||
}
|
||||
|
||||
/// Returns the fragment's hydration ID.
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> &Option<HydrationKey> {
|
||||
&self.id
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
/// Adds an optional marker indicating the view macro source.
|
||||
pub fn with_view_marker(mut self, marker: impl Into<String>) -> Self {
|
||||
self.view_marker = Some(marker.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoView for Fragment {
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "</>", skip_all, fields(children = self.nodes.len())))]
|
||||
fn into_view(self) -> View {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
use cfg_if::cfg_if;
|
||||
use std::fmt;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use crate::Mountable;
|
||||
use wasm_bindgen::JsCast;
|
||||
} else {
|
||||
use crate::hydration::HydrationKey;
|
||||
}
|
||||
}
|
||||
|
||||
use crate::{hydration::HydrationCtx, Comment, CoreComponent, IntoView, View};
|
||||
|
||||
/// The internal representation of the [`Unit`] core-component.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct UnitRepr {
|
||||
comment: Comment,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
pub(crate) id: Option<HydrationKey>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for UnitRepr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("<() />")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UnitRepr {
|
||||
fn default() -> Self {
|
||||
let id = HydrationCtx::id();
|
||||
|
||||
Self {
|
||||
comment: Comment::new("<() />", &id, true),
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
impl Mountable for UnitRepr {
|
||||
#[inline(always)]
|
||||
fn get_mountable_node(&self) -> web_sys::Node {
|
||||
self.comment.node.clone().unchecked_into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_opening_node(&self) -> web_sys::Node {
|
||||
self.comment.node.clone().unchecked_into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_closing_node(&self) -> web_sys::Node {
|
||||
self.comment.node.clone().unchecked_into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The unit `()` leptos counterpart.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Unit;
|
||||
|
||||
impl IntoView for Unit {
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "trace", name = "<() />", skip_all)
|
||||
)]
|
||||
fn into_view(self) -> crate::View {
|
||||
let component = UnitRepr::default();
|
||||
|
||||
View::CoreComponent(CoreComponent::Unit(component))
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
use crate::{html::AnyElement, HtmlElement};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Trait for a directive handler function.
|
||||
/// This is used so it's possible to use functions with one or two
|
||||
/// parameters as directive handlers.
|
||||
///
|
||||
/// You can use directives like the following.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::{*, html::AnyElement};
|
||||
///
|
||||
/// // This doesn't take an attribute value
|
||||
/// fn my_directive(el: HtmlElement<AnyElement>) {
|
||||
/// // do sth
|
||||
/// }
|
||||
///
|
||||
/// // This requires an attribute value
|
||||
/// fn another_directive(el: HtmlElement<AnyElement>, params: i32) {
|
||||
/// // do sth
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyComponent() -> impl IntoView {
|
||||
/// view! {
|
||||
/// // no attribute value
|
||||
/// <div use:my_directive></div>
|
||||
///
|
||||
/// // with an attribute value
|
||||
/// <div use:another_directive=8></div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// A directive is just syntactic sugar for
|
||||
///
|
||||
/// ```ignore
|
||||
/// let node_ref = create_node_ref();
|
||||
///
|
||||
/// create_effect(move |_| {
|
||||
/// if let Some(el) = node_ref.get() {
|
||||
/// directive_func(el, possibly_some_param);
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// A directive can be a function with one or two parameters.
|
||||
/// The first is the element the directive is added to and the optional
|
||||
/// second is the parameter that is provided in the attribute.
|
||||
pub trait Directive<T: ?Sized, P> {
|
||||
/// Calls the handler function
|
||||
fn run(&self, el: HtmlElement<AnyElement>, param: P);
|
||||
}
|
||||
|
||||
impl<F> Directive<(HtmlElement<AnyElement>,), ()> for F
|
||||
where
|
||||
F: Fn(HtmlElement<AnyElement>),
|
||||
{
|
||||
fn run(&self, el: HtmlElement<AnyElement>, _: ()) {
|
||||
self(el)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, P> Directive<(HtmlElement<AnyElement>, P), P> for F
|
||||
where
|
||||
F: Fn(HtmlElement<AnyElement>, P),
|
||||
{
|
||||
fn run(&self, el: HtmlElement<AnyElement>, param: P) {
|
||||
self(el, param);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, P> Directive<T, P> for Rc<dyn Directive<T, P>> {
|
||||
fn run(&self, el: HtmlElement<AnyElement>, param: P) {
|
||||
(**self).run(el, param)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, P> Directive<T, P> for Box<dyn Directive<T, P>> {
|
||||
fn run(&self, el: HtmlElement<AnyElement>, param: P) {
|
||||
(**self).run(el, param);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user