Compare commits

..

550 Commits

Author SHA1 Message Date
Greg Johnston
2abb56a9fc chore: clippy 2024-07-24 09:22:20 -04:00
Greg Johnston
ca0c2a6280 example: use a local signal for the input 2024-07-24 09:20:09 -04:00
Greg Johnston
50026be1ae feat: mark branches in AnyView 2024-07-24 09:15:54 -04:00
Greg Johnston
d6e7f44a6a chore: clippy 2024-07-24 09:09:03 -04:00
Greg Johnston
b77559af51 chore: remove unused imports 2024-07-24 09:07:43 -04:00
Greg Johnston
8ce42747fd fix: improve type inference for the default threadsafe signal case 2024-07-24 09:06:27 -04:00
Greg Johnston
8245324989 chore: clippy 2024-07-24 08:15:32 -04:00
Greg Johnston
8f75eca6a6 fix: add missing imports 2024-07-24 08:14:31 -04:00
Greg Johnston
8a22e33d3c docs: fix cfg for docsrs 2024-07-24 07:56:57 -04:00
Greg Johnston
98f849e28e chore: remove unnecessary default generics 2024-07-24 07:56:57 -04:00
Greg Johnston
cf765549fb chore: clippy and fmt 2024-07-24 07:56:57 -04:00
Saber Haj Rabiee
48ded323a8 fix: hackernews_js_fetch example for leptos_0.7 (#2678) 2024-07-24 07:56:57 -04:00
Greg Johnston
acbd96059b feat: allow !Send signals 2024-07-24 07:56:57 -04:00
Greg Johnston
01755bbd82 fix: rename nightly feature for const generic &'static str (d0c11bf6e3\#diff-7b65e42e2b87910c94950caf7f0687fda2f9f98f311099404f5c4afb4a36e50c) 2024-07-24 07:56:57 -04:00
Greg Johnston
75e681a977 chore: remove pub field in Suspend so that Suspend::new() must be used 2024-07-24 07:56:57 -04:00
Greg Johnston
835d4a3e64 chore: fmt 2024-07-24 07:56:57 -04:00
Greg Johnston
90ea25aee1 fix: only create Future once initially, and poll it twice, rather than creating it twice 2024-07-24 07:56:57 -04:00
Greg Johnston
505f192e6a chore: fmt 2024-07-24 07:56:57 -04:00
Greg Johnston
8b0943be06 feat: optional branch-marking in HTML to support initial work on client-side islands routing 2024-07-24 07:56:57 -04:00
Corvus
845447e0ce feat: reintroduce queue_microtask (#2703) 2024-07-24 07:56:57 -04:00
Greg Johnston
78601d69ae fix: allow one-element tuples for route matching 2024-07-24 07:56:57 -04:00
Greg Johnston
a40c591039 example: use path! macro in router example 2024-07-24 07:56:57 -04:00
Greg Johnston
decec8c580 fix: add HTML global on___ attributes 2024-07-24 07:56:57 -04:00
boyswan
e4749dc1a9 feat: add path! macro in router to parse string paths into tuples (#2694) 2024-07-24 07:56:57 -04:00
mahmoud-eltahawy
74112f0690 feat: allow using enums for StaticSegment by implementing AsPath (#2685) 2024-07-24 07:56:57 -04:00
Greg Johnston
0e5a3a4317 fix: set None observer properly in ScopedFuture 2024-07-24 07:56:57 -04:00
Greg Johnston
e0640f92dd fix: remove unnecessary untrack in Show 2024-07-24 07:56:57 -04:00
Greg Johnston
c6a209c0d5 fix: Suspend::new() in router 2024-07-24 07:56:57 -04:00
Greg Johnston
eda56e2758 examples: update to Suspend::new() 2024-07-24 07:56:57 -04:00
Greg Johnston
35a88ee9e4 docs: improved warning location 2024-07-24 07:56:56 -04:00
Greg Johnston
adb7adacd7 fix: require Suspend::new() to ensure the Future is properly scoped at creation time, not at render time 2024-07-24 07:56:56 -04:00
Greg Johnston
22762ecf85 fix: explicitly untrack the children of <Show/> 2024-07-24 07:56:56 -04:00
Greg Johnston
093723ce2e fix: custom elements should support any attribute names 2024-07-24 07:56:56 -04:00
Greg Johnston
71c94d53bf fix: custom elements SSR 2024-07-24 07:56:56 -04:00
Greg Johnston
b88e4637e9 docs: add tachys docs 2024-07-24 07:56:56 -04:00
Greg Johnston
a4fca5bef1 chore: start with a default sandbox, to avoid panics in tests 2024-07-24 07:56:56 -04:00
Greg Johnston
0b87451ab2 docs: finish reactive graph docs for 0.7 2024-07-24 07:56:56 -04:00
Greg Johnston
523786cd4d docs: porting docs from 0.6 to 0.7 2024-07-24 07:56:56 -04:00
Greg Johnston
3e2f69e6a8 chore: remove unused AsyncState 2024-07-24 07:56:56 -04:00
Bruno De Simone
afbc5be655 add allow too_many_arguments (#2684) 2024-07-24 07:56:56 -04:00
Greg Johnston
d937739f26 chore: clippy 2024-07-24 07:56:56 -04:00
Greg Johnston
7bdba127c0 chore: clippy 2024-07-24 07:56:56 -04:00
Greg Johnston
b1724cc336 chore: clear warnings 2024-07-24 07:56:56 -04:00
Greg Johnston
7ca9b485e5 feat: Suspend on style: and class: 2024-07-24 07:56:56 -04:00
Greg Johnston
a25994612f fix: proper building of paths for nested fields 2024-07-24 07:56:56 -04:00
Greg Johnston
6a1732def4 feat: type-erased store Field structs 2024-07-24 07:56:56 -04:00
Greg Johnston
d07579f278 fix: allow creating resources inside Suspense 2024-07-24 07:56:56 -04:00
Greg Johnston
9a58c639ca examples: revert changes to counter 2024-07-24 07:56:56 -04:00
Bruno De Simone
da9b26fd90 remove FromRef implementation from LeptosRoutes axum impl (#2670) 2024-07-24 07:56:56 -04:00
luoxiaozero
7d5f7d3d05 feat: additional ARIA attributes (#2677) 2024-07-24 07:56:56 -04:00
Greg Johnston
d1a71ef1df feat: iteration over reactive store list 2024-07-24 07:56:56 -04:00
Greg Johnston
f517276b2e feat: add arena-allocated/Copy Store 2024-07-24 07:56:56 -04:00
Greg Johnston
44b4268e6a docs: initial work on porting docs from 0.6 to 0.7 2024-07-24 07:56:56 -04:00
Greg Johnston
6923f52d12 feat: initial work on reactive stores 2024-07-24 07:56:56 -04:00
Greg Johnston
59a3c5538e fix: improved API for unsync actions that doesn't require SendWrapper on input 2024-07-24 07:56:56 -04:00
Greg Johnston
3f4a897e60 chore: leptosfmt 2024-07-24 07:56:56 -04:00
Greg Johnston
6355126fd7 fix: properly handle errors in streaming body responses 2024-07-24 07:56:56 -04:00
Greg Johnston
596cd72303 fix: invalid p/ul relationship causing hydration issue 2024-07-24 07:56:56 -04:00
Saber Haj Rabiee
2cac35b910 chore: cargo fmt (#2672) 2024-07-24 07:56:56 -04:00
Greg Johnston
f92520ce98 feat: use codee for shared ser-de codexes with leptos-use (and more possibilities in future) 2024-07-24 07:56:56 -04:00
Greg Johnston
ecc4e588eb chore: remove unused dependencies 2024-07-24 07:56:56 -04:00
Greg Johnston
4289470059 fix: was disposing of Suspense Owner too early 2024-07-24 07:56:56 -04:00
Greg Johnston
60eb68a856 fix: rebuilding NodeRef 2024-07-24 07:56:56 -04:00
Greg Johnston
a0f5ea2189 feat: top-level Suspend without Suspense 2024-07-24 07:56:56 -04:00
Greg Johnston
67bebf385f chore: clippy and clean up unused functions 2024-07-24 07:56:56 -04:00
Greg Johnston
56af98c19f docs: update syntax for Html/Body 2024-07-24 07:56:56 -04:00
Greg Johnston
21101ce44d feat: correct HTML rendering for spread attributes on <Body/> and <Html/> 2024-07-24 07:56:56 -04:00
Greg Johnston
f11163f904 feat: attributes on body and html 2024-07-24 07:56:56 -04:00
Greg Johnston
a36467c117 feat: move to a channel-based implementation for meta 2024-07-24 07:56:56 -04:00
Greg Johnston
2d39922156 feat: support Suspend in attributes 2024-07-24 07:56:56 -04:00
Greg Johnston
ee2d1fb4d3 feat: local resources with .await 2024-07-24 07:56:56 -04:00
Greg Johnston
105b2d088f chore: cfg warnings 2024-07-24 07:56:56 -04:00
Greg Johnston
48c83890a8 fix: MaybeProp None case 2024-07-24 07:56:56 -04:00
Greg Johnston
2699ec2120 reexport SVG and MathML element types 2024-07-24 07:56:56 -04:00
Greg Johnston
390e917ca4 router alpha 3 2024-07-24 07:56:56 -04:00
Greg Johnston
114c1c1cba Form component in 0.7 2024-07-24 07:56:56 -04:00
Greg Johnston
d8c5e67866 fix: don't break Routes SSR now that it uses Suspend 2024-07-24 07:56:56 -04:00
Alicia Garcia-Raboso
fc368f6767 Implement Default trait for signals with a parameter that also implements Default (#2662)
Co-authored-by: alicia.garcia.raboso <alicia.garcia.raboso@bbva.com>
2024-07-24 07:56:56 -04:00
Greg Johnston
63860a1b26 feat: automatically replace Suspense blocks if they are still waiting to be flushed, without JS (replaces PartiallyBlocked) 2024-07-24 07:56:56 -04:00
Greg Johnston
c7f9eb8be2 blocking resources 2024-07-24 07:56:56 -04:00
Greg Johnston
367852a8dc macro alpha 2 2024-07-24 07:56:56 -04:00
Greg Johnston
1792836113 router alpha 2 2024-07-24 07:56:56 -04:00
Luke Naylor
dbd38f1004 Add missing 'form' attribute for <mo> (#2660)
This attribute seems to be missing in the attribute
table on Mozilla Docs, however does appear in the
compatibility table lower down.
This attribute is also frequently used by temml,
a common generator for mathml content.
2024-07-24 07:56:56 -04:00
Greg Johnston
a402a260a9 allow Outlet to be called multiple times 2024-07-24 07:56:56 -04:00
Greg Johnston
b3ba7cf12f fix: prevent panicking if conditionally rendering Outlet 2024-07-24 07:56:56 -04:00
Greg Johnston
0d60051845 remove log 2024-07-24 07:56:56 -04:00
Greg Johnston
df8ca27875 fix: ensure correct ownership chain when passing views through Outlet 2024-07-24 07:56:56 -04:00
Greg Johnston
637bf25317 chore: clippy 2024-07-24 07:56:56 -04:00
Greg Johnston
7cced5d011 support MaybeSignal in view 2024-07-24 07:56:56 -04:00
Kajetan Welc
154f43f3d6 fix: impl Copy for Callback (#2658) 2024-07-24 07:56:56 -04:00
Greg Johnston
e632c8981a fix: update imports and methods 2024-07-24 07:56:56 -04:00
Greg Johnston
1c9428dd2c fix: trait import 2024-07-24 07:56:56 -04:00
Greg Johnston
90dd481e94 impl From<T> for (Arc)Signal<T> 2024-07-24 07:56:56 -04:00
Greg Johnston
9c717ab035 Revert "lazy Future construction for AsyncDerived"
This reverts commit 9e84e1f57c.
2024-07-24 07:56:56 -04:00
Greg Johnston
08f933bcd8 fix: <option> struct generation 2024-07-24 07:56:56 -04:00
Greg Johnston
ffcd0cd27e fix: <option> struct generation 2024-07-24 07:56:56 -04:00
Lucas Åström
d4a0110871 Destructuring let (0.7) (#2655)
* Use `let()` syntax for bindings

This lets users use destructuring when binding more complex values, and we also get better IDE support.

* Update rstml
2024-07-24 07:56:56 -04:00
Greg Johnston
503271e833 fix: nonexistent feature 2024-07-24 07:56:56 -04:00
Greg Johnston
cdb43831b9 fix: rebuilding AnyView 2024-07-24 07:56:56 -04:00
Greg Johnston
e1e7703ec1 support wasm-only 2024-07-24 07:56:56 -04:00
Greg Johnston
d89b64b317 support wasm-only 2024-07-24 07:56:56 -04:00
Greg Johnston
e22f1a721f support wasm-only 2024-07-24 07:56:56 -04:00
Greg Johnston
61c177e7d5 any_spawner tick version 2024-07-24 07:56:56 -04:00
Greg Johnston
2c8454ac60 un-break circular dependency 2024-07-24 07:56:56 -04:00
Greg Johnston
dba780680a chore: update test output 2024-07-24 07:56:56 -04:00
Greg Johnston
5e9218b495 chore: unused import 2024-07-24 07:56:56 -04:00
Greg Johnston
fa679a08a1 restore fallback for compressed version 2024-07-24 07:56:56 -04:00
Greg Johnston
8e68aea7f5 docs: working on memo docs 2024-07-24 07:56:56 -04:00
Greg Johnston
8727642f00 0.7.0-alpha 2024-07-24 07:56:56 -04:00
Greg Johnston
c56b0160ca feat: support reactive and asynchronous ProtectedRoute conditions 2024-07-24 07:56:56 -04:00
Greg Johnston
b6285f6427 fix: correct For behavior when mounting with siblings, and when clearing 2024-07-24 07:56:56 -04:00
Ben Wishovich
868d34f746 Make get_configuration sync (#2647)
* Made get_configuraiton sync

* Update examples
2024-07-24 07:56:56 -04:00
Greg Johnston
da9985bdcd examples: update directives tests 2024-07-24 07:56:55 -04:00
Greg Johnston
250bae1e5a chore: cargo fmt 2024-07-24 07:56:55 -04:00
Greg Johnston
9f9f445031 make RemoveEventHandler a concrete type 2024-07-24 07:56:55 -04:00
Greg Johnston
f9bcaff77e feat: add ElementExt to give access to the same view APIs at runtime that we do at compile time 2024-07-24 07:56:55 -04:00
Greg Johnston
3ca0154e8d feat: add ElementExt to give access to the same view APIs at runtime that we do at compile time 2024-07-24 07:56:55 -04:00
Greg Johnston
4d72ca05b6 chore: cargo fmt 2024-07-24 07:56:55 -04:00
Greg Johnston
879a075129 chore: cargo fmt 2024-07-24 07:56:55 -04:00
Greg Johnston
6be5583633 docs: warn on unused RenderEffect 2024-07-24 07:56:55 -04:00
Greg Johnston
c2381ce983 chore: missing Debug implementations 2024-07-24 07:56:55 -04:00
Greg Johnston
d8ac849cef chore: suppress unnecessary .into() warning 2024-07-24 07:56:55 -04:00
Greg Johnston
8c12d6ec30 cargo fmt 2024-07-24 07:56:55 -04:00
Greg Johnston
dbefaed343 updated directives example 2024-07-24 07:56:55 -04:00
brofrain
a69ec96e75 fix: update HtmlViewState & BodyViewState as well 2024-07-24 07:56:55 -04:00
Kajetan Welc
6f349cf630 fix: do not accidentally mount things before meta tags in the <head> when updating the DOM v2 2024-07-24 07:56:55 -04:00
Greg Johnston
6d0f027d45 remove log 2024-07-24 07:56:55 -04:00
Greg Johnston
ca29be7f08 preliminary work on directives (not useful yet until we have an ElementExt that allows you to do things declaratively from an Element 2024-07-24 07:56:55 -04:00
Greg Johnston
0d7f2c8c2c omit () entirely if it is the only child of an HTML element 2024-07-24 07:56:55 -04:00
Greg Johnston
8067b269bf fix: don't override a text node's 'next sibling after text' position if it's in Either, now that they don't have separate marker nodes 2024-07-24 07:56:55 -04:00
Greg Johnston
cc0198244f fix: do not accidentally mount things before meta tags in the <head> when updating the DOM 2024-07-24 07:56:55 -04:00
Greg Johnston
fbc2461c2c fix docs for hydrate_islands 2024-07-24 07:56:55 -04:00
Greg Johnston
dc03ba0a28 hackernews islands example 2024-07-24 07:56:55 -04:00
Greg Johnston
5787e0c91f update static file serving in Axum examples 2024-07-24 07:56:55 -04:00
Greg Johnston
1e7cf132cb fix islands ci setup 2024-07-24 07:56:55 -04:00
Greg Johnston
6cf69fe08c fix: remove extra comment at end of Suspense now that Either no longer requires it 2024-07-24 07:56:55 -04:00
Greg Johnston
a512fb8be3 feat: provide static file handling/fallback directly in integration 2024-07-24 07:56:55 -04:00
Greg Johnston
91c6c340aa islands example 2024-07-24 07:56:55 -04:00
Greg Johnston
32605f4e96 remove unused tests and dependencies 2024-07-24 07:56:55 -04:00
Greg Johnston
5c956026a0 fix: () in templates 2024-07-24 07:56:55 -04:00
Greg Johnston
d34718ad31 chore: clippy 2024-07-24 07:56:55 -04:00
Greg Johnston
092822d318 fix js-framework-benchmark for stable 2024-07-24 07:56:55 -04:00
Greg Johnston
9ed1acd906 update hackernews_axum to 0.7 2024-07-24 07:56:55 -04:00
Greg Johnston
cab828da6f fix ErrorBoundary starting in error state in CSR 2024-07-24 07:56:55 -04:00
Greg Johnston
3ae26d4aef fix example tests 2024-07-24 07:56:55 -04:00
Greg Johnston
d737ab62c6 remove unnecessary logs 2024-07-24 07:56:55 -04:00
Greg Johnston
b77e640f58 update islands example 2024-07-24 07:56:55 -04:00
Greg Johnston
242ef36f87 chore: unused hooks 2024-07-24 07:56:55 -04:00
Greg Johnston
37e78df9e9 regression test for 7094dee150 2024-07-24 07:56:55 -04:00
Greg Johnston
03330336aa fix: signals mark subscribers dirty, but don't say they're always dirty if they haven't changed 2024-07-24 07:56:55 -04:00
Greg Johnston
8c0a5107f7 make Routes fallback run lazily 2024-07-24 07:56:55 -04:00
Greg Johnston
e7cc1b7f49 make ErrorBoundary fallback run lazily 2024-07-24 07:56:55 -04:00
Greg Johnston
0beb620aba examples: errors_axum 2024-07-24 07:56:55 -04:00
Greg Johnston
2cd576ebd3 chore(ci): fix examples 2024-07-24 07:56:55 -04:00
Greg Johnston
58ac7cdf77 enable reactive-graph hydration when hydration is enabled 2024-07-24 07:56:55 -04:00
Greg Johnston
fcb5d63e47 allow conversion directly from Arc signal types to MaybeSignal 2024-07-24 07:56:55 -04:00
Greg Johnston
c306a40d3b expose Owner::shared_context() 2024-07-24 07:56:55 -04:00
Greg Johnston
c32b21329a 0.7 Provider component 2024-07-24 07:56:55 -04:00
Greg Johnston
bca905e617 fix tests 2024-07-24 07:56:55 -04:00
Greg Johnston
27ee65a7e8 fix reactive styles 2024-07-24 07:56:55 -04:00
Greg Johnston
8640b36b8e fix: correctly rebuild reactive attributes to avoid stale signals 2024-07-24 07:56:55 -04:00
Greg Johnston
77b6255f3c type-erase RenderEffeect functions for binary size improvements 2024-07-24 07:56:55 -04:00
luoxiaozero
f00b8e02c6 feat: Attr exposes PhantomData field (#2641) 2024-07-24 07:56:55 -04:00
Greg Johnston
20ce0f543e chore(ci): remove warnings in tests 2024-07-24 07:56:55 -04:00
Greg Johnston
d40e8ac297 feat: 0.7 query signals 2024-07-24 07:56:55 -04:00
Greg Johnston
f6e83698de chore(ci): add Makefiles for smaller packages 2024-07-24 07:56:55 -04:00
Greg Johnston
a93212c533 chore(ci): only run semver checks if not labeled 'breaking' 2024-07-24 07:56:55 -04:00
Greg Johnston
8e107b042e chore: clippy 2024-07-24 07:56:55 -04:00
Greg Johnston
05dded3f48 rename from new_serde to new 2024-07-24 07:56:55 -04:00
Greg Johnston
153f9af497 remove most remaining marker/placeholder elements 2024-07-24 07:56:55 -04:00
Greg Johnston
1c3c161a1b add trait impls and encodings for SharedValue 2024-07-24 07:56:55 -04:00
Greg Johnston
a91eab4f5b default to SerdeJson encoding for resources, use new_str() for Str encoding 2024-07-24 07:56:55 -04:00
Greg Johnston
255fa85925 feat: synchronous serialized values with SharedValue 2024-07-24 07:56:55 -04:00
Greg Johnston
da77d0fb36 whenever we create a new root Owner, set it as the current owner, which will make it the default owner (e.g., during SSR) instead of None 2024-07-24 07:56:55 -04:00
Greg Johnston
0e360b7e00 fix: can't memoize JS properties, because they can be set between signal updates by user input 2024-07-24 07:56:55 -04:00
Greg Johnston
404de33fb3 fix: correctly escape style and class attributes 2024-07-24 07:56:55 -04:00
Greg Johnston
6712656738 chore: clippy 2024-07-24 07:56:55 -04:00
Greg Johnston
38bb187735 don't require spawn_local for actios 2024-07-24 07:56:55 -04:00
Greg Johnston
f57f0831b8 catch resource reads inside Signal during Suspense 2024-07-24 07:56:55 -04:00
Greg Johnston
1c2b201e1f fix CSS file names 2024-07-24 07:56:55 -04:00
Greg Johnston
2058cee348 update todo app csr 2024-07-24 07:56:55 -04:00
Greg Johnston
140996db6e fix: correctly escape text nodes, except in script/style tags 2024-07-24 07:56:55 -04:00
Greg Johnston
4c9adc7d50 chore: clean up examples for CI 2024-07-24 07:56:55 -04:00
Greg Johnston
196d845b86 fix: provide matched route via context when rebuilding (so <A> works) 2024-07-24 07:56:55 -04:00
Greg Johnston
3dec67a51e unused 2024-07-24 07:56:55 -04:00
Greg Johnston
abe4b39a01 refactor insert_before_this to find parent lazily, and use it for rebuilding reactive components by replacing their whole contents 2024-07-24 07:56:55 -04:00
Greg Johnston
99d8541c83 make sure SendWrapper supports Futures 2024-07-24 07:56:55 -04:00
Greg Johnston
37e5e89c78 move several complex examples into projects 2024-07-24 07:56:55 -04:00
Greg Johnston
5e11ea1753 update js-framework-benchmark example 2024-07-24 07:56:55 -04:00
Greg Johnston
8c54f7b8cf fix test text 2024-07-24 07:56:55 -04:00
Greg Johnston
f6b265ce7f fix: custom Stream implementation for streaming resource data that supports nested data/multiple polled values, rather than taking it all at once at the beginning 2024-07-24 07:56:55 -04:00
Greg Johnston
8b0b24ad72 suspense_tests: actually wait for other resource in nested case 2024-07-24 07:56:55 -04:00
Greg Johnston
69ac71ab04 can save a lookup here 2024-07-24 07:56:55 -04:00
Greg Johnston
7c9ff835e4 remove unused workspace member 2024-07-24 07:56:54 -04:00
Greg Johnston
261856e6f3 update workflows 2024-07-24 07:56:54 -04:00
Greg Johnston
32976b2de4 remove unused leptos_reactive integration 2024-07-24 07:56:54 -04:00
Greg Johnston
508e38d48d remove old router files 2024-07-24 07:56:54 -04:00
Greg Johnston
48d125222a fix Cargo.toml after merge 2024-07-24 07:56:54 -04:00
Greg Johnston
49fb0f681c re-enable all routes 2024-07-24 07:56:54 -04:00
Greg Johnston
7737376672 chore: clean up warnings and logging 2024-07-24 07:56:54 -04:00
Greg Johnston
c9091ffcdc reverted Fn()/FnMut() change 2024-07-24 07:56:54 -04:00
Greg Johnston
2e3a976288 Revert "fix: constrain reactive rendering to Fn(), because using dry_resolve() for Suspense requires idempotent render functions so that they can be called once (to register resources) and called a second time to resolve"
This reverts commit 7ec5c77ba3e8f45bae04a7661a56741f95125adb.
2024-07-24 07:56:54 -04:00
Greg Johnston
e044ab3070 progress on updating suspense tests 2024-07-24 07:56:54 -04:00
Greg Johnston
0a5764868f add server redirects 2024-07-24 07:56:54 -04:00
Greg Johnston
bee7a2c13a fix: relative path resolution 2024-07-24 07:56:54 -04:00
Greg Johnston
b3b1f11b1c simplifying todo examples 2024-07-24 07:56:54 -04:00
Greg Johnston
78d0c75ef4 initial work updating suspense tests 2024-07-24 07:56:54 -04:00
Greg Johnston
a53a30dbed clarify hydrate/csr warning 2024-07-24 07:56:54 -04:00
Greg Johnston
10d5bd704e export actions in prelude 2024-07-24 07:56:54 -04:00
Greg Johnston
69da1f2198 remove Into<_> by default for setting signals, because it interferes with type inference 2024-07-24 07:56:54 -04:00
Greg Johnston
5e32da1690 add support for unsync actions 2024-07-24 07:56:54 -04:00
Greg Johnston
a6ad908cae simplifying and updating server fns example 2024-07-24 07:56:54 -04:00
Greg Johnston
d8bf76963f change name to shell 2024-07-24 07:56:54 -04:00
Greg Johnston
78c1ab6546 update control flow components to new Fn() constraint 2024-07-24 07:56:54 -04:00
Greg Johnston
6abd75463e fix: constrain reactive rendering to Fn(), because using dry_resolve() for Suspense requires idempotent render functions so that they can be called once (to register resources) and called a second time to resolve 2024-07-24 07:56:54 -04:00
Greg Johnston
a395fcdb95 add Debug impl 2024-07-24 07:56:54 -04:00
Greg Johnston
56cfb95df8 fix: actually concatenate nested routes during route generation 2024-07-24 07:56:54 -04:00
Greg Johnston
7d323103c5 reorganize Outlet export 2024-07-24 07:56:54 -04:00
Greg Johnston
0bd39a58ec examples: porting to 0.7 and cleaning up 2024-07-24 07:56:54 -04:00
Greg Johnston
d6ba60a9b0 fix merge 2024-07-24 07:56:54 -04:00
Rakshith Ravi
c9cc2a140f Update import statements in examples (#2625) 2024-07-24 07:56:54 -04:00
Greg Johnston
988aadeef2 examples: use application 404 page 2024-07-24 07:56:54 -04:00
Greg Johnston
e3d1daa87b chore: cargo fmt 2024-07-24 07:56:54 -04:00
Greg Johnston
76d7a5f619 chore: cargo fmt 2024-07-24 07:56:54 -04:00
Greg Johnston
a8be8b2ca9 add warnings if correct features not set for browser 2024-07-24 07:56:54 -04:00
Greg Johnston
6f2467de06 use csr feature so that reactivity runs 2024-07-24 07:56:54 -04:00
Greg Johnston
6bcafb49e4 chore: clippy 2024-07-24 07:56:54 -04:00
Greg Johnston
44fa16dde3 examples: fix input type so tests work, and update text to make the purpose clearer 2024-07-24 07:56:54 -04:00
Greg Johnston
64a4e59266 remove leptos_reactive (moved into reactive_graph and leptos_server) 2024-07-24 07:56:54 -04:00
Greg Johnston
722d77b128 fix: Clone for ArcResource and default to SerdeJson for Resource 2024-07-24 07:56:54 -04:00
Greg Johnston
24b3d9988a chore: clippy 2024-07-24 07:56:54 -04:00
Greg Johnston
aac902c349 fix: don't dispose of parent owners before Suspense children have been rendered 2024-07-24 07:56:54 -04:00
Greg Johnston
644081860f warn if trying to use meta on server side without context 2024-07-24 07:56:54 -04:00
Greg Johnston
0f225daa40 only run RenderEffects when effects are enabled 2024-07-24 07:56:54 -04:00
Greg Johnston
933e015d24 add set_pending to <Transition/> 2024-07-24 07:56:54 -04:00
Greg Johnston
11c2ba553f simplify FlatRoutes logic by using existing OwnedView infrastructure 2024-07-24 07:56:54 -04:00
Greg Johnston
43675b7741 reexport tick() for testing 2024-07-24 07:56:54 -04:00
Greg Johnston
c70c9681e3 fix: ensure that leptos_meta and leptos_router are in SSR mode if using one of the server integrations 2024-07-24 07:56:54 -04:00
Greg Johnston
55725ea11e examples: update hackernews for SSR support 2024-07-24 07:56:54 -04:00
Greg Johnston
41dc042ee7 fix: correctly notify multiple subscribers to same AsyncDerived 2024-07-24 07:56:54 -04:00
Greg Johnston
16f66c5e52 fix: Routes SSR 2024-07-24 07:56:54 -04:00
Greg Johnston
6ff7bca5de fix: correct owner for HTML rendering in FlatRoutes 2024-07-24 07:56:54 -04:00
Greg Johnston
1df939d41e testing: provide tick() that can be called anywhere in tests 2024-07-24 07:56:54 -04:00
Greg Johnston
4845cf989f testing: provide tick() that can be called anywhere in tests 2024-07-24 07:56:54 -04:00
Greg Johnston
ffe09346cd fix portal tests 2024-07-24 07:56:54 -04:00
Greg Johnston
b591140291 fix cleanups in render effects 2024-07-24 07:56:54 -04:00
Greg Johnston
d4f35fb41f chore: clippy 2024-07-24 07:56:54 -04:00
Greg Johnston
d343b373c7 update counters_isomorphic 2024-07-24 07:56:54 -04:00
Greg Johnston
b0c3bb9b10 reexport spawn and spawn_local 2024-07-24 07:56:54 -04:00
Greg Johnston
32a8433d0b ReadSignal from stream 2024-07-24 07:56:54 -04:00
Greg Johnston
1571c907b2 refactor to allow rendering Resource directly in view 2024-07-24 07:56:54 -04:00
Greg Johnston
2baccc8615 removed AnimatedShow example (duplicates the component docs) 2024-07-24 07:56:54 -04:00
Greg Johnston
74fa1da243 properly serialize errors 2024-07-24 07:56:54 -04:00
Greg Johnston
ec617214eb chore: clear warning 2024-07-24 07:56:54 -04:00
Greg Johnston
1b351f5139 pick up on server action error in both server and client 2024-07-24 07:56:54 -04:00
Greg Johnston
1682b6a543 fmt and chores in examples 2024-07-24 07:56:54 -04:00
Greg Johnston
8d634c0d35 fix: serialize an empty string into HTML so it still works as a text node 2024-07-24 07:56:54 -04:00
Greg Johnston
0f820152ca fix: make router fallback lazy 2024-07-24 07:56:54 -04:00
Greg Johnston
a270b7c7c3 add expect_context 2024-07-24 07:56:54 -04:00
Greg Johnston
da824e3e38 fix attr:class when spreading onto a component 2024-07-24 07:56:54 -04:00
Greg Johnston
0255b3991b add ServerAction error handling for any error type (closes #2325) 2024-07-24 07:56:54 -04:00
Greg Johnston
9c44e486f6 remove unused import 2024-07-24 07:56:54 -04:00
Greg Johnston
652c153c6b clean up example 2024-07-24 07:56:54 -04:00
Greg Johnston
b7d9dde973 add CollectView 2024-07-24 07:56:54 -04:00
Greg Johnston
92df0fb145 Actix todo_app_sqlite 2024-07-24 07:56:54 -04:00
Greg Johnston
93bb2e1803 fix: don't drop Owner in FlatRoutes until route has been rendered (thanks @benwis) 2024-07-24 07:56:54 -04:00
Greg Johnston
db25d08ba9 fix: make sure all resource reads are registered 2024-07-24 07:56:54 -04:00
Greg Johnston
48dad26797 chore: clippy and unused dependencies in integrations 2024-07-24 07:56:54 -04:00
Greg Johnston
0b29246972 add some tracing and debug info to HTML elements 2024-07-24 07:56:54 -04:00
Greg Johnston
f63811fbda refactor integrations and add Actix integration 2024-07-24 07:56:54 -04:00
Greg Johnston
a78e649a03 feat: 0.7 nonce support 2024-07-24 07:56:54 -04:00
Greg Johnston
7e23c05f77 ResponseOptions support 2024-07-24 07:56:54 -04:00
Greg Johnston
ad36abed67 fix counters tests 2024-07-24 07:56:54 -04:00
Greg Johnston
7aeade70a6 allow .children() on HTML elements 2024-07-24 07:56:54 -04:00
Greg Johnston
471ab8fbe7 remove async demo 2024-07-24 07:56:54 -04:00
Greg Johnston
7ae5b29f47 fix: writing to lock that has a read 2024-07-24 07:56:54 -04:00
Greg Johnston
f12957a6b2 allow untracking on write guards to support maybe_update 2024-07-24 07:56:54 -04:00
Greg Johnston
b3fd881a8f feat: allow .write() on all writeable signals 2024-07-24 07:56:54 -04:00
Greg Johnston
a70cc73c14 feat: add .by_ref() to create a Future from an AsyncDerived (etc.) that takes a reference, rather than cloning 2024-07-24 07:56:54 -04:00
Greg Johnston
097f3c377a use impl trait in props 2024-07-24 07:56:54 -04:00
Greg Johnston
a711531034 update wasm-bindgen testing approaches 2024-07-24 07:56:54 -04:00
Greg Johnston
b5552fc83e chore: clearing warnings in examples 2024-07-24 07:56:54 -04:00
Greg Johnston
947929b827 additional warnings 2024-07-24 07:56:54 -04:00
Greg Johnston
d5027a6b2d cargo fmt 2024-07-24 07:56:54 -04:00
Greg Johnston
ed061ec6a0 chore: clear up... a few warnings 2024-07-24 07:56:54 -04:00
Greg Johnston
d10cca93f8 update sledgehammer integration 2024-07-24 07:56:53 -04:00
Greg Johnston
1103683e7f remove signal function setter Send-only implementation (dead code) 2024-07-24 07:56:53 -04:00
Greg Johnston
42835815ac remove signal function call Read implementations (dead code) 2024-07-24 07:56:53 -04:00
Greg Johnston
f31dafb398 remove leptos_reactive dependency 2024-07-24 07:56:53 -04:00
Greg Johnston
fa8b58992b oco merge issues 2024-07-24 07:56:53 -04:00
Greg Johnston
db11a387e1 feat: return an async guard from .await rather than cloning the value every time 2024-07-24 07:56:53 -04:00
Greg Johnston
fa92cc21e2 fix return type in async tests 2024-07-24 07:56:53 -04:00
Greg Johnston
e2aa2390fc fix tests that run effects 2024-07-24 07:56:53 -04:00
Greg Johnston
541f734341 feat: return Option from AsyncDerived.get() instead of AsyncState 2024-07-24 07:56:53 -04:00
Greg Johnston
5ed1f29485 example: restore ErrorBoundary 2024-07-24 07:56:53 -04:00
Greg Johnston
aa88a54b74 docs for Owner and context 2024-07-24 07:56:53 -04:00
Greg Johnston
c2d91e78e7 fix tests 2024-07-24 07:56:53 -04:00
Greg Johnston
8d7177ef9c poll AsyncDerived synchronously so that it has the correct value during hydration if it reads from a resource 2024-07-24 07:56:53 -04:00
Greg Johnston
61a6f64b90 correct dirty-checking on AsyncDerived 2024-07-24 07:56:53 -04:00
Greg Johnston
f735ae8d5e fix regular suspense if nothing was read synchronously 2024-07-24 07:56:53 -04:00
Greg Johnston
341840f958 missing dry_resolve on Static 2024-07-24 07:56:53 -04:00
Greg Johnston
3d2e971a93 feat: support *either* .await or reactive reads inside Suspense 2024-07-24 07:56:53 -04:00
Greg Johnston
e245418df8 feat: 0.7 slots 2024-07-24 07:56:53 -04:00
Greg Johnston
d6ab394280 feat: enhanced spreading syntax 2024-07-24 07:56:53 -04:00
Greg Johnston
1f59d98f67 fix external navigations 2024-07-24 07:56:53 -04:00
Greg Johnston
7f4f289fba make WindowListenerHandle Send + Sync so it can be remove via on_cleanup 2024-07-24 07:56:53 -04:00
Greg Johnston
be72416b76 restore ssr/hydration for Routes 2024-07-24 07:56:53 -04:00
Greg Johnston
d878a800ea get nested Routes working again 2024-07-24 07:56:53 -04:00
Greg Johnston
bec7ac8f2e default to Params::get() giving an owned value (which you want in a derived signal), but use reference in the macro 2024-07-24 07:56:53 -04:00
Greg Johnston
5bde17008e add proper dirty checking on AsyncDerived so it can read from memos properly 2024-07-24 07:56:53 -04:00
Greg Johnston
920b340085 make NavigateOptions pub 2024-07-24 07:56:53 -04:00
Greg Johnston
e71e36f70c feat: owning memo 2024-07-24 07:56:53 -04:00
Greg Johnston
a4952b0c78 remove warnings in tests and only run if effects are enabled 2024-07-24 07:56:53 -04:00
Greg Johnston
5fe0e1fa0b fix: prevent memos that have changed from re-triggering the running effect, by setting the Observer during .update_if_necessary() 2024-07-24 07:56:53 -04:00
Greg Johnston
ecb973f74a feat: add Popover API 2024-07-24 07:56:53 -04:00
Greg Johnston
8c1ed5b464 fix Script children 2024-07-24 07:56:53 -04:00
Greg Johnston
0600d7460c fix hydration of Suspend by including the missing placeholder it expects during hydration 2024-07-24 07:56:53 -04:00
Greg Johnston
8fa6fae868 add missing marker comments for Result 2024-07-24 07:56:53 -04:00
Greg Johnston
f98c032aa9 include marker comments in html len 2024-07-24 07:56:53 -04:00
Greg Johnston
281d6841de unused owner 2024-07-24 07:56:53 -04:00
Greg Johnston
fe5943a9f0 impl From/Into for Signal/ArcSignal 2024-07-24 07:56:53 -04:00
Greg Johnston
fbc29586b2 add ArcSignal::derive() 2024-07-24 07:56:53 -04:00
Greg Johnston
e80e9a89ab routing progress indicator 2024-07-24 07:56:53 -04:00
Greg Johnston
66578a94d3 missing min attribute 2024-07-24 07:56:53 -04:00
Greg Johnston
29c6ab1e0b clean up 2024-07-24 07:56:53 -04:00
Greg Johnston
2aa3a1c9d8 remove log 2024-07-24 07:56:53 -04:00
Greg Johnston
94da23184a relax trait bounds on reactive types where possible 2024-07-24 07:56:53 -04:00
Greg Johnston
ed0152d270 add SignalSetter 2024-07-24 07:56:53 -04:00
Greg Johnston
8ea8b0bf47 use transition between navigations 2024-07-24 07:56:53 -04:00
Greg Johnston
b32bcdb0c8 add async transitions that wait for any AsyncDerived created/triggered under them before resolving 2024-07-24 07:56:53 -04:00
Greg Johnston
1a217423dc resolve() on OwnedView 2024-07-24 07:56:53 -04:00
Greg Johnston
0a9a8736ed restore hydration feature for some of its feature-gating benefits for Resource deserialization 2024-07-24 07:56:53 -04:00
Greg Johnston
bd80ea2eb2 cargo fmt 2024-07-24 07:56:53 -04:00
Greg Johnston
c0f6d2fde8 create separate URL/params signals for each route, to prevent updating them and running side effects while navigating away 2024-07-24 07:56:53 -04:00
Greg Johnston
dd3eccae87 fmt 2024-07-24 07:56:53 -04:00
Greg Johnston
7447c0f58e distinguish between dirty and check in effects, so that memos and signals both work correctly 2024-07-24 07:56:53 -04:00
Greg Johnston
2b406e0473 remove unused feature 2024-07-24 07:56:53 -04:00
Greg Johnston
c89ef0de77 updated future impls 2024-07-24 07:56:53 -04:00
Greg Johnston
9f1225b48f check whether ArcAsyncDerived actually needs to run when marked check 2024-07-24 07:56:53 -04:00
Greg Johnston
c0b9d5937b add ancestry debugging for owners 2024-07-24 07:56:53 -04:00
Greg Johnston
682c3cd24c lazy Future construction for AsyncDerived 2024-07-24 07:56:53 -04:00
Greg Johnston
e235b7088d support Resource in CSR for backward-compat 2024-07-24 07:56:53 -04:00
Greg Johnston
a72e1d5c23 allow let: syntax to work 2024-07-24 07:56:53 -04:00
Greg Johnston
6cd82630b7 scope Suspense/Transition correctly within ownership tree 2024-07-24 07:56:53 -04:00
Greg Johnston
056456538e upgrading hackernews example 2024-07-24 07:56:53 -04:00
Greg Johnston
14d664d15b immediately commit URL signal updates 2024-07-24 07:56:53 -04:00
Greg Johnston
fbbbd08e5c reexport A from router::components 2024-07-24 07:56:53 -04:00
Greg Johnston
8bf3f0d8dd add IntoAny to tachys prelude 2024-07-24 07:56:53 -04:00
Greg Johnston
cc0f5096c7 finish support for innerHTML 2024-07-24 07:56:53 -04:00
Greg Johnston
15c11e9a5d feat: iterating over items in children with ChildrenFragment, ChildrenFragmentFn, ChildrenFragmentMut 2024-07-24 07:56:53 -04:00
Greg Johnston
209f8cfc48 only warn about non-reactive accesses if effects are enabled 2024-07-24 07:56:53 -04:00
Greg Johnston
84d6301f71 only run effects on client 2024-07-24 07:56:53 -04:00
Greg Johnston
0792319ae1 resolve() implementation for AnyView 2024-07-24 07:56:53 -04:00
Greg Johnston
4c08d99095 fix deadlock on nested Signals 2024-07-24 07:56:53 -04:00
Greg Johnston
1320470d14 fix FlatRouter SSR/hydration after lazy routes 2024-07-24 07:56:53 -04:00
Greg Johnston
433cf151bb feat: nested islands with context for 0.7 2024-07-24 07:56:53 -04:00
Greg Johnston
7ed3a0d5e6 fix: correct Send + Sync bounds for children 2024-07-24 07:56:53 -04:00
Greg Johnston
21a95fe574 feat: minimal island support in 0.7 2024-07-24 07:56:53 -04:00
Greg Johnston
6ac31c05e1 docs: full docs and doctests for Action/MultiAction 2024-07-24 07:56:53 -04:00
Greg Johnston
452189b49a remove support for rendering guards directly, as they are !Send and holding onto them in State is also a bad idea 2024-07-24 07:56:53 -04:00
Greg Johnston
4ff89d0baa support nightly static values for style:key="value" 2024-07-24 07:56:53 -04:00
Greg Johnston
13692b84f1 revert to using .get() for function calls 2024-07-24 07:56:53 -04:00
Greg Johnston
80597581d7 fix: only rerun effects if they have dirty ancestors (or it's the first run) 2024-07-24 07:56:53 -04:00
Greg Johnston
f3fdc6c9fe start working on porting over docs and tests and 0.7... 2024-07-24 07:56:53 -04:00
Greg Johnston
bf81e3077c fix meta issue with attributes 2024-07-24 07:56:53 -04:00
Greg Johnston
8fbdea92a3 fix tracing issue 2024-07-24 07:56:53 -04:00
Greg Johnston
b75559de8e chore: get tests in a working state 2024-07-24 07:56:53 -04:00
Greg Johnston
f4e0ec865d fix: make Selector Send/Sync 2024-07-24 07:56:53 -04:00
Greg Johnston
ab9a9d6235 docs: runtime warning if you use .track() outside a tracking context 2024-07-24 07:56:53 -04:00
Greg Johnston
75214f3d65 preliminary tracing for tachys 2024-07-24 07:56:53 -04:00
Greg Johnston
a09ca6c999 chore: warnings 2024-07-24 07:56:53 -04:00
Greg Johnston
b497bae682 examples: router in 0.7 2024-07-24 07:56:53 -04:00
Greg Johnston
daf2a77b73 fix: passing context through router 2024-07-24 07:56:53 -04:00
Greg Johnston
98fe3c707c chore: fix warnings about variable case 2024-07-24 07:56:53 -04:00
Greg Johnston
4e2e94f0b1 examples: timer in 0.7 2024-07-24 07:56:53 -04:00
Greg Johnston
9affa1cdc8 feat: Portals in 0.7 2024-07-24 07:56:53 -04:00
Greg Johnston
e878ffbe7b allow either eager or lazy routes 2024-07-24 07:56:53 -04:00
Greg Johnston
9c5ef51cc3 update StoredValue API in callbacks 2024-07-24 07:56:53 -04:00
Greg Johnston
dd86f9502d chore: clippy warnings 2024-07-24 07:56:53 -04:00
Greg Johnston
33046ca84a smooth out StoredValue APIs 2024-07-24 07:56:53 -04:00
Greg Johnston
cd766f82c9 MaybeSignal and MaybeProp 2024-07-24 07:56:53 -04:00
Marc-Stefan Cassola
6620f85307 added a few old deprecated functions to help users port (#2580) 2024-07-24 07:56:53 -04:00
Greg Johnston
40259990d8 implement With(Untracked) for Signal 2024-07-24 07:56:53 -04:00
Greg Johnston
2dc685a1de don't over-rerender nested router 2024-07-24 07:56:53 -04:00
Greg Johnston
0617c2d803 initial async routing work (to support bundle splitting) 2024-07-24 07:56:53 -04:00
Greg Johnston
462ffae95f noop attribute 'spreading' for routers 2024-07-24 07:56:53 -04:00
Greg Johnston
a98cc28c37 support arbitrary attributes on components in view 2024-07-24 07:56:52 -04:00
Greg Johnston
c365e885cd full attribute spreading 2024-07-24 07:56:52 -04:00
Greg Johnston
945222726b update counter_without_macros imports 2024-07-24 07:56:52 -04:00
Greg Johnston
161c968211 stashing 2024-07-24 07:56:52 -04:00
Greg Johnston
c4aa720766 give a route to upgrade any attribute into a cloneable one 2024-07-24 07:56:52 -04:00
Greg Johnston
8616fab88b work on attribute spreading 2024-07-24 07:56:52 -04:00
Greg Johnston
d8bda86f1c disable AddAnyAttr again now that I remember why it was broken 2024-07-24 07:56:52 -04:00
Greg Johnston
0128e0486d reenable AnyAttr 2024-07-24 07:56:52 -04:00
Greg Johnston
2c24fa9b71 reorganizing exports and updating examples 2024-07-24 07:56:52 -04:00
Greg Johnston
3572652db2 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
5db973ded5 fix reorganized exports 2024-07-24 07:56:52 -04:00
Greg Johnston
6d81602b98 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
487676df97 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
a6f052c0e4 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
8addbdb64d module restructuring for 0.7 2024-07-24 07:56:52 -04:00
Greg Johnston
2059435144 let ErrorBoundary own the fallback 2024-07-24 07:56:52 -04:00
Greg Johnston
8d9783a770 make Suspend a transparent wrapper 2024-07-24 07:56:52 -04:00
Greg Johnston
844bad68b8 provide params properly in FlatRouter 2024-07-24 07:56:52 -04:00
Greg Johnston
59120c310d clear some warnings 2024-07-24 07:56:52 -04:00
Greg Johnston
aaa64f5cab rename TupleBuilder to NextTuple and prep for release 2024-07-24 07:56:52 -04:00
Greg Johnston
5ee2544952 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
53ac129e27 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
a46900f56c prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
bdc0ec25f2 prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
ea50d81178 rename any_error 2024-07-24 07:56:52 -04:00
Greg Johnston
58951e499b prep for preview release 2024-07-24 07:56:52 -04:00
Greg Johnston
3f57dab7f1 remove twiggy file 2024-07-24 07:56:52 -04:00
Greg Johnston
b1edf04abb move router crates 2024-07-24 07:56:52 -04:00
Greg Johnston
87ca774d49 comparison demo 2024-07-24 07:56:52 -04:00
Greg Johnston
ad062c0ea3 SSR optimizations for binary size, and flat router 2024-07-24 07:56:52 -04:00
Greg Johnston
499b965305 work on Axum integration and on error boundaries 2024-07-24 07:56:52 -04:00
Greg Johnston
cdd2e06f28 stash 2024-07-24 07:56:52 -04:00
Greg Johnston
aa841cc8ea feat: add <A> 2024-07-24 07:56:52 -04:00
Greg Johnston
060f89c492 add use_navigate and Redirect 2024-07-24 07:56:52 -04:00
Greg Johnston
6268d902d9 preliminary use_navigate work 2024-07-24 07:56:52 -04:00
Greg Johnston
1740ccd329 fix fallback => match update 2024-07-24 07:56:52 -04:00
Greg Johnston
4b44ab47a1 add more hooks and primitives to router 2024-07-24 07:56:52 -04:00
Greg Johnston
247802ab87 nested route CSR working 2024-07-24 07:56:52 -04:00
Greg Johnston
df4be92936 continuing on nested routes 2024-07-24 07:56:52 -04:00
Greg Johnston
3024c54f74 continuing on nested routes 2024-07-24 07:56:52 -04:00
Greg Johnston
8d4501d006 working on reconfiguring nested routing 2024-07-24 07:56:52 -04:00
Greg Johnston
02f256f602 make placeholder-finding code consistent across container types 2024-07-24 07:56:52 -04:00
Greg Johnston
018b212f70 remove logs 2024-07-24 07:56:52 -04:00
Greg Johnston
762ed8685b fix Transition hydration 2024-07-24 07:56:52 -04:00
Greg Johnston
ee2c192b99 remove TryCatch/fallible rendering in favor of better ErrorBoundary model 2024-07-24 07:56:52 -04:00
Greg Johnston
c4e5ba3a61 finish todo_app_sqlite_axum 2024-07-24 07:56:52 -04:00
Greg Johnston
b326f0f4e0 fix Vec hydration 2024-07-24 07:56:52 -04:00
Greg Johnston
5ce5ec3cac add MultiActionForm 2024-07-24 07:56:52 -04:00
Greg Johnston
eda794a1ff add MultiAction/ServerMultiAction 2024-07-24 07:56:52 -04:00
Greg Johnston
fe646d7f6a stash 2024-07-24 07:56:52 -04:00
Greg Johnston
26ec393537 ErrorBoundary SSR and serialization of errors to support hydration 2024-07-24 07:56:52 -04:00
Greg Johnston
e9ab1c0b03 get types working with nested ErrorBoundary/Suspense 2024-07-24 07:56:52 -04:00
Greg Johnston
126a1ebbe9 probably as far as I can go with the current SuspenseBoundary approach 2024-07-24 07:56:52 -04:00
Greg Johnston
9024cec3eb fix static types 2024-07-24 07:56:52 -04:00
Greg Johnston
28bce5d2a6 fix cancellation logic for server fn requests 2024-07-24 07:56:52 -04:00
Greg Johnston
7b7a82a8f1 only subscribe to memo manually if already loaded 2024-07-24 07:56:52 -04:00
Greg Johnston
8d3b737303 Suspense SSR 2024-07-24 07:56:52 -04:00
Greg Johnston
7a5e56df15 updates toward todo_app_sqlite 2024-07-24 07:56:52 -04:00
Greg Johnston
7d3715b480 fix stable examples 2024-07-24 07:56:52 -04:00
Greg Johnston
14b9e76e57 implement rendering traits for signals directly on stable 2024-07-24 07:56:52 -04:00
Greg Johnston
309f373601 suspend!() macro 2024-07-24 07:56:52 -04:00
Greg Johnston
d22f347bbb add Transition 2024-07-24 07:56:52 -04:00
Greg Johnston
fc23614d99 loosen requirements for Show 2024-07-24 07:56:52 -04:00
Greg Johnston
f2f43c1237 working on examples 2024-07-24 07:56:52 -04:00
Greg Johnston
6f71e9d6d6 finish TodoMVC example 2024-07-24 07:56:52 -04:00
Greg Johnston
8317e7edaa simplify Suspense: this should still work with hydration 2024-07-24 07:56:52 -04:00
Greg Johnston
5eae11d1ca working model for Suspense with new version 2024-07-24 07:56:52 -04:00
Greg Johnston
5a0954a61a probably as far as I can go with the current SuspenseBoundary approach 2024-07-24 07:56:52 -04:00
Greg Johnston
d94b29cd05 fix ErrorBoundary/Suspense 2024-07-24 07:56:52 -04:00
Greg Johnston
b79a5422aa feat: ErrorBoundary and Suspense 2024-07-24 07:56:52 -04:00
Greg Johnston
6923eed5fd feat: ErrorBoundary 2024-07-24 07:56:52 -04:00
Greg Johnston
21e21e4f35 fix Cargo.toml merge issues 2024-07-24 07:56:52 -04:00
Greg Johnston
e6973deded working on examples 2024-07-24 07:56:52 -04:00
Greg Johnston
6f63cf7a52 styling with CSS 2024-07-24 07:56:52 -04:00
Greg Johnston
f2c16314be example with isomorphic GTK/web design system 2024-07-24 07:56:52 -04:00
Greg Johnston
e9c649477e gtk example 2024-07-24 07:56:52 -04:00
Greg Johnston
2bb30ef6ed ErrorBoundary component 2024-07-24 07:56:52 -04:00
Greg Johnston
537764d82e Suspense/Transition components 2024-07-24 07:56:52 -04:00
Greg Johnston
c64affe458 GTK example for 0.7 2024-07-24 07:56:51 -04:00
Greg Johnston
488b87bcbf add serde-wasm-bindgen encoding for resources 2024-07-24 07:56:51 -04:00
Greg Johnston
555438c747 add typed children 2024-07-24 07:56:51 -04:00
Greg Johnston
cc04773d5f scope Arena to each request 2024-07-24 07:56:51 -04:00
Greg Johnston
f55f634f95 correctly omit HTML-generating code from AnyView 2024-07-24 07:56:51 -04:00
Greg Johnston
58cd2f92d7 experimental sledgehammer Renderer backend 2024-07-24 07:56:51 -04:00
Greg Johnston
67217aa693 fix async context issues, add flat routing 2024-07-24 07:56:51 -04:00
Greg Johnston
c015695462 test more dynamic string length work 2024-07-24 07:56:51 -04:00
Greg Johnston
1eaa6b6b0b attribute value escaping 2024-07-24 07:56:51 -04:00
Greg Johnston
92f76e9177 work related to 0.7 blog port 2024-07-24 07:56:51 -04:00
Greg Johnston
2a4eb9b484 work related to 0.7 blog port 2024-07-24 07:56:51 -04:00
Greg Johnston
0d5f60c7f2 completing work on meta 2024-07-24 07:56:51 -04:00
Greg Johnston
3be3d72f25 completing work on meta 2024-07-24 07:56:51 -04:00
Greg Johnston
b824f59176 initial work on meta 2024-07-24 07:56:51 -04:00
Greg Johnston
b3a799c6dd initial work on meta 2024-07-24 07:56:51 -04:00
Greg Johnston
2dc462bc8b stash 2024-07-24 07:56:51 -04:00
Greg Johnston
971c57c963 fix nested route rebuilding 2024-07-24 07:56:51 -04:00
Greg Johnston
1f2c9780c2 navigation between nested routes 2024-07-24 07:56:51 -04:00
Greg Johnston
346556082d initial stage for working nested route rendering 2024-07-24 07:56:51 -04:00
Greg Johnston
6a249bd29f stash 2024-07-24 07:56:51 -04:00
Greg Johnston
48c1bca429 stash 2024-07-24 07:56:51 -04:00
Greg Johnston
ee5c5d6f4c get basic routing working 2024-07-24 07:56:51 -04:00
Greg Johnston
2a2f129e40 stash 2024-07-24 07:56:51 -04:00
Greg Johnston
ea09fb253a reorganize 2024-07-24 07:56:51 -04:00
Greg Johnston
ade6f1b7bc working on nesting routing 2024-07-24 07:56:51 -04:00
Greg Johnston
2be7f8d06c reorganize 2024-07-24 07:56:51 -04:00
Greg Johnston
cdfb98adf6 stash 2024-07-24 07:56:51 -04:00
Greg Johnston
b9d37a4571 abstract interface to walk nested routes and to access views 2024-07-24 07:56:51 -04:00
Greg Johnston
0e5dce6d0f reorganize and clean up 2024-07-24 07:56:51 -04:00
Greg Johnston
2ff4e1ea8f nested route matching 2024-07-24 07:56:51 -04:00
Greg Johnston
beff818216 stash 2024-07-24 07:56:51 -04:00
Greg Johnston
ba15a96fe7 stash 2024-07-24 07:56:51 -04:00
Greg Johnston
4c67fb854c work on routing utils 2024-07-24 07:56:51 -04:00
Greg Johnston
1416385fc0 nested route matching working 2024-07-24 07:56:51 -04:00
Greg Johnston
643d2c443b stash 2024-07-24 07:56:51 -04:00
Greg Johnston
1c0aabddd0 use either_of crate 2024-07-24 07:56:51 -04:00
Greg Johnston
a8aaa57e7c nested routes take 1 2024-07-24 07:56:51 -04:00
Greg Johnston
c37f67f858 split EitherOfX into its own crate 2024-07-24 07:56:51 -04:00
Greg Johnston
22f6d9b733 work on routing 2024-07-24 07:56:51 -04:00
Greg Johnston
320848895f work on routing 2024-07-24 07:56:51 -04:00
Greg Johnston
a061102ee4 set up routing 2024-07-24 07:56:51 -04:00
Greg Johnston
10b04c7bae clear warning 2024-07-24 07:56:51 -04:00
Greg Johnston
afbc5de1ad chore: clear warnings 2024-07-24 07:56:51 -04:00
Greg Johnston
7542ca3905 pass on: to components (and lay basis for passing all other attributes) 2024-07-24 07:56:51 -04:00
Greg Johnston
eac71a2c1a working on AddAttr 2024-07-24 07:56:51 -04:00
Greg Johnston
26d7c131a0 remove boilerplate: require that Node, Element, etc. types always be Clone + 'static 2024-07-24 07:56:51 -04:00
Greg Johnston
21e981ab58 parent_child example 2024-07-24 07:56:51 -04:00
Greg Johnston
63de8b321f use AnyError for all try_ rendering errors, so that they can compose 2024-07-24 07:56:51 -04:00
Greg Johnston
71beb92d0e support for guards with class: syntax 2024-07-24 07:56:51 -04:00
Greg Johnston
0ceb6d539a add Borrow implementation to make it easier to abstract over T and Guard<T> 2024-07-24 07:56:51 -04:00
Greg Johnston
343f9299d2 finish error boundary (fix last state transition issue) 2024-07-24 07:56:51 -04:00
Greg Johnston
ce6d751368 progress on error boundary that works with nested reactivity 2024-07-24 07:56:51 -04:00
Greg Johnston
cdcd860608 progress on error boundary that works with nested reactivity 2024-07-24 07:56:51 -04:00
Greg Johnston
7b47457209 error example 2024-07-24 07:56:51 -04:00
Greg Johnston
886411a7b0 enable event delegation 2024-07-24 07:56:51 -04:00
Greg Johnston
8773d57ef0 fix release build 2024-07-24 07:56:51 -04:00
Greg Johnston
8c06c2b62b update TODO.md 2024-07-24 07:56:51 -04:00
Greg Johnston
cf4b0747dc store effects in reactive system 2024-07-24 07:56:51 -04:00
Greg Johnston
80d1ef0e58 todomvc example 2024-07-24 07:56:51 -04:00
Greg Johnston
c69e97bfaf work on async demo 2024-07-24 07:56:51 -04:00
Greg Johnston
a891a7a09e clone values for Futures 2024-07-24 07:56:51 -04:00
Greg Johnston
093e41fc3d make guard types more nestable/flexible so that we can implement render traits on any of them 2024-07-24 07:56:51 -04:00
Greg Johnston
0e8bc744b2 work on async demo 2024-07-24 07:56:51 -04:00
Greg Johnston
557b99426f work on async demo 2024-07-24 07:56:51 -04:00
Greg Johnston
9b0b5138d5 feat: create generic any_spawner crate to share between reactive system and renderer 2024-07-24 07:56:51 -04:00
Greg Johnston
97e4be6f85 add other methods on Stored 2024-07-24 07:56:51 -04:00
Greg Johnston
fb700feffb work on async demo 2024-07-24 07:56:51 -04:00
Greg Johnston
b8f7960f6a stash: working on jsfb 2024-07-24 07:56:51 -04:00
Greg Johnston
d71d39f8ed chore: remove unnecessary log 2024-07-24 07:56:51 -04:00
Greg Johnston
88e1ea9b6b fix: correct owner for rows of For, correct cleanup of arenas 2024-07-24 07:56:51 -04:00
Greg Johnston
5587c5e8b8 fix: close memory leak in tasks waiting on channels 2024-07-24 07:56:51 -04:00
Greg Johnston
be725cb27e feat: typed event targets 2024-07-24 07:56:51 -04:00
Greg Johnston
167474ee55 working on examples 2024-07-24 07:56:51 -04:00
Greg Johnston
ba88bdd082 begin migrating to leptos and leptos_dom packages 2024-07-24 07:56:51 -04:00
Greg Johnston
eee18f8b43 stash 2024-07-24 07:56:51 -04:00
Greg Johnston
ccc01e590f stash 2024-07-24 07:56:18 -04:00
Greg Johnston
a8ea282271 chore: remove leptos_reactive and add reactive_graph 2024-07-24 07:56:18 -04:00
Greg Johnston
5c2f0df4b7 feat: improved ergonomics of read guards 2024-07-24 07:56:18 -04:00
Greg Johnston
46151c4b9f feat: add Readable implementation for all types 2024-07-24 07:56:18 -04:00
Greg Johnston
0fc1692337 feat: add no_std support in appropriate crates 2024-07-24 07:56:18 -04:00
Greg Johnston
511db7ec16 feat: tachys 2024-07-24 07:56:18 -04:00
Greg Johnston
28404e7ee4 feat: add Fn traits 2024-07-24 07:56:18 -04:00
Greg Johnston
e1bb490fa0 docs: note re: execution order (see #2261 and #2262) 2024-07-24 07:56:18 -04:00
Greg Johnston
f6b41535cf feat: modular SharedContext for hydration 2024-07-24 07:56:18 -04:00
Greg Johnston
611ee4c74c chore: split OrPoisoned trait into its own crate for reuse 2024-07-24 07:56:18 -04:00
Greg Johnston
a22242cf53 feat: modular, trait-based, Send/Sync reactive system 2024-07-24 07:56:18 -04:00
261 changed files with 13965 additions and 6664 deletions

View File

@@ -1,13 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "cargo"
directories:
- "/"
- "/examples/*"
- "/benchmarks"
schedule:
interval: "daily"

View File

@@ -14,8 +14,8 @@ jobs:
test:
needs: [get-leptos-changed]
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)
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)
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-08-01
rust-toolchain: nightly-2024-04-14

View File

@@ -49,4 +49,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly-2024-08-01
toolchain: nightly-2024-04-14

View File

@@ -26,7 +26,7 @@ jobs:
- name: Get example project directories that changed
id: changed-dirs
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v41
with:
dir_names: true
dir_names_max_depth: "2"

View File

@@ -21,7 +21,7 @@ jobs:
- name: Get example files that changed
id: changed-files
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v43
with:
files: |
examples/**

View File

@@ -21,7 +21,7 @@ jobs:
- name: Get source files that changed
id: changed-source
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v43
with:
files: |
any_error/**

View File

@@ -64,7 +64,7 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v3
name: Install pnpm
id: pnpm-install
with:

View File

@@ -37,39 +37,37 @@ members = [
"router_macro",
"any_error",
]
exclude = ["benchmarks", "examples", "projects"]
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.7.0-beta2"
edition = "2021"
rust-version = "1.76"
version = "0.7.0-alpha"
rust-version = "1.75"
[workspace.dependencies]
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" }
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" }
[profile.release]
codegen-units = 1

View File

@@ -12,8 +12,6 @@
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

View File

@@ -1,13 +1,13 @@
[package]
name = "throw_error"
version = "0.2.0-beta2"
edition = "2021"
version = "0.1.0"
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.14"
pin-project-lite = "0.2"

View File

@@ -9,7 +9,7 @@ use std::{
error,
fmt::{self, Display},
future::Future,
mem, ops,
ops,
pin::Pin,
sync::Arc,
task::{Context, Poll},
@@ -92,25 +92,9 @@ 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>) -> ResetErrorHookOnDrop {
ResetErrorHookOnDrop(
ERROR_HOOK.with_borrow_mut(|this| mem::replace(this, Some(hook))),
)
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) {
ERROR_HOOK.with_borrow_mut(|this| *this = Some(hook))
}
/// Invokes the error hook set by [`set_error_hook`] with the given error.
@@ -156,10 +140,9 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let _hook = this
.hook
.as_ref()
.map(|hook| set_error_hook(Arc::clone(hook)));
if let Some(hook) = &this.hook {
set_error_hook(Arc::clone(hook))
}
this.inner.poll(cx)
}
}

View File

@@ -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.30"
glib = { version = "0.20.0", optional = true }
thiserror = "1.0"
tokio = { version = "1.39", optional = true, default-features = false, features = [
"rt",
futures = "0.3"
glib = { version = "0.19", optional = true }
thiserror = "1"
tokio = { version = "1", optional = true, default-features = false, features = [
"rt",
] }
tracing = { version = "0.1.40", optional = true }
wasm-bindgen-futures = { version = "0.4.42", optional = true }
tracing = { version = "0.1", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
[features]
tracing = ["dep:tracing"]

View File

@@ -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.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"
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"
[dependencies.web-sys]
version = "0.3.0"
version = "0.3"
features = ["Window", "Document", "HtmlElement", "HtmlInputElement"]

View File

@@ -1,5 +1,6 @@
[package]
name = "const_str_slice_concat"
edition = "2021"
version = "0.1.0"
authors = ["Greg Johnston"]
license = "MIT"
@@ -7,6 +8,5 @@ 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]

View File

@@ -1,5 +1,6 @@
[package]
name = "either_of"
edition = "2021"
version = "0.1.0"
authors = ["Greg Johnston"]
license = "MIT"
@@ -7,7 +8,6 @@ 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.14"
pin-project-lite = "0.2"

View File

@@ -7,18 +7,19 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
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"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
cfg-if = "1"
leptos = { path = "../../leptos" }
leptos_meta = { path = "../../meta" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
wasm-bindgen = "0.2.93"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
[features]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:actix-files",

View File

@@ -1,9 +1,68 @@
# Action Form Error Handling Example
<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>
## Getting Started
# Leptos Starter Template
See the [Examples README](../README.md) for setup and run instructions.
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.
## Quick Start
## Creating your template repo
Execute `cargo leptos watch` to run this example.
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`.

View File

@@ -52,10 +52,23 @@ async fn main() -> std::io::Result<()> {
.await
}
#[cfg(not(feature = "ssr"))]
#[cfg(not(any(feature = "ssr", feature = "csr")))]
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);
}

View File

@@ -10,12 +10,12 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
gloo-timers = { version = "0.3.0", features = ["futures"] }
[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
web-sys = "0.3.70"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -11,34 +11,34 @@ codegen-units = 1
lto = true
[dependencies]
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"
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"
leptos = { path = "../../leptos" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
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 }
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 }
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]

View File

@@ -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>
}

View File

@@ -13,6 +13,6 @@ leptos_router = { path = "../../router", features = [] }
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
web-sys = "0.3.70"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -14,10 +14,10 @@ console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.42"
pretty_assertions = "1.4"
rstest = "0.22.0"
wasm-bindgen-test = "0.3.34"
pretty_assertions = "1.3.0"
rstest = "0.17.0"
[dev-dependencies.web-sys]
features = ["HtmlElement", "XPathResult"]
version = "0.3.70"
version = "0.3.61"

View File

@@ -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.42"
wasm-bindgen = "0.2.93"
web-sys = "0.3.70"
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = "0.3"

View File

@@ -5,12 +5,12 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
log = "0.4.22"
console_log = "1.0"
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"
web-sys = { version = "0.3.70", features = ["Clipboard", "Navigator"] }
web-sys = { version = "0.3", features = ["Clipboard", "Navigator"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.93"
web-sys = { version = "0.3.70", features = ["NodeList"] }
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["NodeList"] }

View File

@@ -24,7 +24,11 @@ pub fn copy_to_clipboard(el: Element, content: &str) {
evt.prevent_default();
evt.stop_propagation();
let _ = window().navigator().clipboard().write_text(&content);
let _ = window()
.navigator()
.clipboard()
.expect("navigator.clipboard to be available")
.write_text(&content);
el.set_inner_html(&format!("Copied \"{}\"", &content));
});
@@ -49,6 +53,7 @@ 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>();
@@ -77,17 +82,12 @@ 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>
// 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>
// `5.into()` automatically called
<button use:add_dot=5>"Add 5 dots"</button>
}
}

View File

@@ -9,6 +9,6 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -7,19 +7,19 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
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" }
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" }
thiserror = "1.0"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[features]
hydrate = ["leptos/hydrate"]

View File

@@ -9,16 +9,16 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
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"
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"
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3"

View File

@@ -4,15 +4,15 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
leptos = { path = "../../leptos", features = ["csr"] }
throw_error = { path = "../../any_error/" }
# these are used to build the integration
gtk = { version = "0.9.0", package = "gtk4" }
any_spawner = { path = "../../any_spawner/" }
next_tuple = { path = "../../next_tuple/" }
paste = "1.0"
gtk = { version = "0.8.0", package = "gtk4", optional = true }
paste = "1.0.14"
# 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"] }
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"]

View File

@@ -56,12 +56,11 @@ 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 {
if let Some(parent) = self.0.parent() {
child.mount(&Element(parent), Some(self));
return true;
}
false
fn insert_before_this(&self,
child: &mut dyn Mountable<LeptosGtk>,
) -> bool {
child.mount(parent, Some(self.as_ref()));
true
}
}
@@ -80,8 +79,11 @@ 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 {
self.0.insert_before_this(child)
fn insert_before_this(&self,
child: &mut dyn Mountable<LeptosGtk>,
) -> bool {
child.mount(parent, Some(self.as_ref()));
true
}
}
@@ -330,12 +332,16 @@ 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 {
self.widget.insert_before_this(child)
fn insert_before_this(&self,
child: &mut dyn Mountable<LeptosGtk>,
) -> bool {
child.mount(parent, Some(self.widget.as_ref()));
true
}
}

View File

@@ -1,8 +1,20 @@
use any_spawner::Executor;
use gtk::{prelude::*, Application, ApplicationWindow, Orientation};
use leptos::prelude::*;
use leptos_gtk::LeptosGtk;
#[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 std::{mem, thread, time::Duration};
#[cfg(feature = "gtk")]
mod leptos_gtk;
const APP_ID: &str = "dev.leptos.Counter";
@@ -10,39 +22,59 @@ 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
_ = Executor::init_glib();
let app = Application::builder().application_id(APP_ID).build();
#[cfg(feature = "gtk")]
{
_ = 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`
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();
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();
let mut state = view.build();
state.mount(&body().into(), None);
mem::forget((owner, state));
});
app.run();
}
}
fn ui() -> impl Render<LeptosGtk> {
#[cfg(feature = "gtk")]
type Rndr = LeptosGtk;
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
type Rndr = Dom;
fn ui() -> impl Render<Rndr> {
let value = RwSignal::new(0);
let rows = RwSignal::new(vec![1, 2, 3, 4, 5]);
Effect::new(move |_| {
println!("value = {}", value.get());
logging::log!("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);
@@ -50,10 +82,7 @@ fn ui() -> impl Render<LeptosGtk> {
vstack((
hstack((
button("-1", move || {
println!("clicked -1");
value.update(|n| *n -= 1);
}),
button("-1", move || value.update(|n| *n -= 1)),
move || value.get().to_string(),
button("+1", move || value.update(|n| *n += 1)),
)),
@@ -62,36 +91,75 @@ fn ui() -> impl Render<LeptosGtk> {
items.swap(1, 3);
})
}),
hstack(rows),
hstack(For(ForProps::builder()
.each(move || rows.get())
.key(|k| *k)
.children(|v| v)
.build())),
))
}
fn button(
label: impl Render<LeptosGtk>,
label: impl Render<Rndr>,
callback: impl Fn() + Send + Sync + 'static,
) -> impl Render<LeptosGtk> {
leptos_gtk::button()
.child(label)
.connect("clicked", move |_| {
callback();
None
})
) -> 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)
}
}
fn vstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
leptos_gtk::r#box()
.orientation(Orientation::Vertical)
.spacing(12)
.child(children)
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 hstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
leptos_gtk::r#box()
.orientation(Orientation::Horizontal)
.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)
}
}
#[cfg(feature = "gtk")]
fn load_css() {
use gtk::{gdk::Display, CssProvider};

View File

@@ -13,27 +13,31 @@ panic = "abort"
lto = true
[dependencies]
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"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_log = "1"
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_meta = { path = "../../meta" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
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"] }
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"] }
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"

View File

@@ -11,22 +11,22 @@ codegen-units = 1
lto = true
[dependencies]
console_error_panic_hook = "0.1.7"
console_error_panic_hook = "0.1"
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.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"
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"
send_wrapper = { version = "0.6.0", features = ["futures"] }
[features]

View File

@@ -11,35 +11,34 @@ codegen-units = 1
lto = true
[dependencies]
console_error_panic_hook = "0.1.7"
leptos = { path = "../../leptos", features = ["experimental-islands"] }
console_error_panic_hook = "0.1"
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.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 = [
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 = [
"fs",
"compression-gzip",
"compression-br",
], 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 }
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 }
[features]
default = []
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = [

View File

@@ -11,30 +11,30 @@ codegen-units = 1
lto = true
[dependencies]
console_error_panic_hook = "0.1.7"
console_error_panic_hook = "0.1"
console_log = "1.0"
log = "0.4.22"
log = "0.4"
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.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 = [
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 = [
"AbortController",
"AbortSignal",
"Request",
"Response",
] }
getrandom = { version = "0.2.15", features = ["js"] }
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = { version = "0.4.42", features = [
getrandom = { version = "0.2.7", features = ["js"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4.37", features = [
"futures-core-03-stream",
], optional = true }
axum-js-fetch = { git = "https://github.com/seanaye/axum-js-fetch", optional = true }

View File

@@ -7,32 +7,32 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
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.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"
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"
[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]

View File

@@ -7,35 +7,33 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
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.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"
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"
[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]

View File

@@ -8,13 +8,13 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `delegation` features
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `event-delegation` features
# used in rand, but we need to enable js feature
getrandom = { version = "0.2.15", features = ["js"] }
getrandom = { version = "0.2.7", 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.42"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -9,7 +9,7 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
web-sys = "0.3.70"
web-sys = "0.3"

View File

@@ -5,12 +5,12 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
log = "0.4.22"
console_log = "1.0"
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.93"
web-sys = "0.3.70"
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = "0.3"

View File

@@ -10,16 +10,16 @@ codegen-units = 1
panic = "abort"
[dependencies]
console_log = "1.0"
console_log = "1"
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.0", features = ["derive"] }
futures = "0.3.30"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
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.42"
wasm-bindgen-test = "0.3.0"

View File

@@ -72,7 +72,6 @@ pub fn ContactRoutes() -> impl MatchNestedRoutes<Dom> + Clone {
<Route path=path!("/:id") view=Contact/>
</ParentRoute>
}
.into_inner()
}
#[component]

View File

@@ -7,52 +7,44 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", 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 }
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 }
thiserror = "1.0"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
serde_toml = "0.0.1"
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 }
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 }
[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]

View File

@@ -9,6 +9,6 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -7,20 +7,20 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
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" }
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"}
leptos_meta = { path = "../../meta" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
tokio = { version = "1.39", features = ["time"] }
wasm-bindgen = "0.2.93"
log = "0.4"
serde = { version = "1", features = ["derive"] }
thiserror = "1"
tokio = { version = "1", features = ["time"] }
wasm-bindgen = "0.2"
[features]
hydrate = ["leptos/hydrate"]

View File

@@ -7,39 +7,37 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
console_log = "1.0"
lazy_static = "1.5"
leptos = { path = "../../leptos", features = [
"hydration",
] } #"nightly", "hydration"] }
console_error_panic_hook = "0.1"
console_log = "1"
lazy_static = "1"
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.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",
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",
], optional = true }
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[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]

View File

@@ -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,24 +64,15 @@ 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>
@@ -148,15 +139,9 @@ 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>
@@ -219,11 +204,12 @@ 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),
@@ -251,13 +237,17 @@ 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

View File

@@ -15,6 +15,6 @@ reactive_stores_macro = { path = "../../reactive_stores_macro" }
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
web-sys = "0.3.70"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -7,26 +7,26 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1.7"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
wasm-bindgen = "0.2.93"
serde = "1.0"
tokio = { version = "1.39", features = ["time", "rt"], optional = true }
log = "0.4"
wasm-bindgen = "0.2"
serde = "1.0.159"
tokio = { version = "1.29", 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]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dev-dependencies]
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"
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"
[[test]]
name = "app_suite"

View File

@@ -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

View File

@@ -13,15 +13,19 @@ leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
# dependencies for browser (enable when hydrate set)
console_error_panic_hook = { version = "0.1.7", optional = true }
wasm-bindgen = { version = "0.2.93", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
wasm-bindgen = { version = "0.2", optional = true }
# dependencies for server (enable when ssr set)
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", features = ["macros"], optional = true }
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", 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",

View File

@@ -7,35 +7,32 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
axum = { version = "0.7.5", optional = true }
console_error_panic_hook = "0.1.7"
axum = { version = "0.7", optional = true }
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_meta = { path = "../../meta" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_router = { path = "../../router" }
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"
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"
thiserror = "1.0"
tracing = { version = "0.1.40", optional = true }
http = "1.1"
tracing = { version = "0.1", optional = true }
http = "1.0"
[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]

View File

@@ -7,5 +7,5 @@ edition = "2021"
leptos = { path = "../../leptos", features = ["csr"] }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
gloo-net = { version = "0.6.0", features = ["http"] }
console_error_panic_hook = { version = "0.1.7" }
gloo-net = { version = "0.5", features = ["http"] }
console_error_panic_hook = { version = "0.1" }

View File

@@ -8,15 +8,15 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3.70"
version = "0.3"
features = ["Window"]
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3.0"

View File

@@ -7,36 +7,36 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
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"
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"
console_error_panic_hook = "0.1.7"
serde = { version = "1.0", features = ["derive"] }
futures = "0.3.30"
leptos = { path = "../../leptos" }
serde = { version = "1.0.152", features = ["derive"] }
futures = "0.3.25"
leptos = { path = "../../leptos"}
leptos_actix = { path = "../../integrations/actix", optional = true }
log = "0.4.22"
simple_logger = "5.0"
log = "0.4.17"
simple_logger = "4.0.0"
gloo = { git = "https://github.com/rustwasm/gloo" }
sqlx = { version = "0.8.0", features = [
"runtime-tokio-rustls",
"sqlite",
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
wasm-bindgen = "0.2.93"
tokio = { version = "1.39", features = ["rt", "time"], optional = true }
wasm-bindgen = "0.2"
tokio = { version = "1", 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]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dev-dependencies]
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"
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"
[[test]]
name = "app_suite"

View File

@@ -8,35 +8,35 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "1.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
leptos = { path = "../../leptos", features = ["tracing"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
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",
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",
], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[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]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dev-dependencies]
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"
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"
[[test]]
name = "app_suite"

View File

@@ -7,39 +7,39 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
console_error_panic_hook = "0.1"
futures = "0.3"
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.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",
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",
], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[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]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dev-dependencies]
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"
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"
[[test]]
name = "app_suite"

View File

@@ -8,15 +8,19 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
leptos = { path = "../../leptos" }
console_error_panic_hook = "0.1.7"
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"] }
uuid = { version = "1", features = ["v4", "js", "serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
web-sys = { version = "0.3.60", features = ["Storage"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3.0"
[features]
default = ["csr"]
csr = ["leptos/csr"]
[package.metadata.cargo-all-features]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,22 +1,22 @@
[package]
name = "hydration_context"
version = "0.2.0-beta2"
edition = "2021"
version = "0.2.0-alpha"
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.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"
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"
pin-project-lite = "0.2.14"
[features]

View File

@@ -12,48 +12,42 @@ 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.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)),
)
})
__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)),
)
})
.collect()
})
})
.collect()
}
fn incomplete_chunks() -> Vec<SerializedDataId> {
__INCOMPLETE_CHUNKS.with(|i| {
i.iter()
.map(|value| {
let id = value.as_f64().unwrap() as usize;
SerializedDataId(id)
})
.collect()
})
__INCOMPLETE_CHUNKS
.iter()
.map(|value| {
let id = value.as_f64().unwrap() as usize;
SerializedDataId(id)
})
.collect()
}
/// An error that has been serialized across the network boundary.
@@ -124,7 +118,7 @@ impl SharedContext for HydrateSharedContext {
fn write_async(&self, _id: SerializedDataId, _fut: PinnedFuture<String>) {}
fn read_data(&self, id: &SerializedDataId) -> Option<String> {
__RESOLVED_RESOURCES.with(|r| r.get(id.0 as u32).as_string())
__RESOLVED_RESOURCES.get(id.0 as u32).as_string()
}
fn await_data(&self, _id: &SerializedDataId) -> Option<String> {

View File

@@ -44,12 +44,6 @@ 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.

View File

@@ -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.8"
actix-web = "4.8"
futures = "0.3.30"
actix-http = "3"
actix-web = "4"
futures = "0.3"
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.0"
parking_lot = "0.12.3"
tracing = "0.1.40"
tokio = { version = "1.39", features = ["rt", "fs"] }
serde_json = "1"
parking_lot = "0.12.1"
tracing = "0.1.37"
tokio = { version = "1", features = ["rt", "fs"] }
send_wrapper = "0.6.0"
[package.metadata.docs.rs]

View File

@@ -16,7 +16,6 @@ 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},
@@ -182,20 +181,6 @@ 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
@@ -316,9 +301,8 @@ 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, method)
server_fn::actix::get_server_fn_service(path)
{
let owner = Owner::new();
owner
@@ -400,7 +384,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::prelude::*;
/// use leptos::*;
/// use leptos_router::Method;
/// use std::{env, net::SocketAddr};
///
@@ -422,7 +406,11 @@ pub fn handle_server_fns_with_context(
/// // the actual routing will be handled by `leptos_router`
/// .route(
/// "/{tail:.*}",
/// leptos_actix::render_app_to_stream(MyApp, Method::Get),
/// leptos_actix::render_app_to_stream(
/// leptos_options.to_owned(),
/// || view! { <MyApp/> },
/// Method::Get,
/// ),
/// )
/// })
/// .bind(&addr)?
@@ -464,7 +452,7 @@ where
/// This can then be set up at an appropriate route in your application:
/// ```
/// use actix_web::{App, HttpServer};
/// use leptos::prelude::*;
/// use leptos::*;
/// use leptos_router::Method;
/// use std::{env, net::SocketAddr};
///
@@ -487,7 +475,8 @@ where
/// .route(
/// "/{tail:.*}",
/// leptos_actix::render_app_to_stream_in_order(
/// MyApp,
/// leptos_options.to_owned(),
/// || view! { <MyApp/> },
/// Method::Get,
/// ),
/// )
@@ -529,7 +518,7 @@ where
/// This can then be set up at an appropriate route in your application:
/// ```
/// use actix_web::{App, HttpServer};
/// use leptos::prelude::*;
/// use leptos::*;
/// use leptos_router::Method;
/// use std::{env, net::SocketAddr};
///
@@ -551,7 +540,11 @@ where
/// // the actual routing will be handled by `leptos_router`
/// .route(
/// "/{tail:.*}",
/// leptos_actix::render_app_async(MyApp, Method::Get),
/// leptos_actix::render_app_async(
/// leptos_options.to_owned(),
/// || view! { <MyApp/> },
/// Method::Get,
/// ),
/// )
/// })
/// .bind(&addr)?
@@ -951,7 +944,7 @@ where
{
let _ = any_spawner::Executor::init_tokio();
let owner = Owner::new_root(Some(Arc::new(SsrSharedContext::new())));
let owner = Owner::new_root(None);
let (mock_meta, _) = ServerMetaContext::new();
let routes = owner
.with(|| {
@@ -1387,21 +1380,18 @@ impl LeptosRoutes for &mut ServiceConfig {
///
/// Any error that occurs during extraction is converted to a [`ServerFnError`].
///
/// ```rust
/// use leptos::prelude::*;
///
/// ```rust,ignore
/// // MyQuery is some type that implements `Deserialize + Serialize`
/// #[server]
/// pub async fn extract_connection_info() -> Result<String, ServerFnError> {
/// use actix_web::dev::ConnectionInfo;
/// pub async fn query_extract() -> Result<MyQuery, ServerFnError> {
/// use actix_web::web::Query;
/// use leptos_actix::*;
///
/// // 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?;
/// let Query(data) = extract().await?;
///
/// // do something with the data
///
/// Ok(format!("{info:?}"))
/// Ok(data)
/// }
/// ```
pub async fn extract<T>() -> Result<T, ServerFnError>
@@ -1409,7 +1399,7 @@ where
T: actix_web::FromRequest,
<T as FromRequest>::Error: Display,
{
let req = use_context::<Request>().ok_or_else(|| {
let req = use_context::<HttpRequest>().ok_or_else(|| {
ServerFnError::new("HttpRequest should have been provided via context")
})?;

View File

@@ -1,153 +1,148 @@
// TODO these tests relate to trailing-slash logic, which is still TBD for 0.7
use leptos::*;
use leptos_actix::generate_route_list;
use leptos_router::{Route, Router, Routes, TrailingSlash};
// 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);
// }
#[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);
}

View File

@@ -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.5", default-features = false, features = [
"matched-path",
axum = { version = "0.7", default-features = false, features = [
"matched-path",
] }
futures = "0.3.30"
http = "1.1"
http-body-util = "0.1.2"
futures = "0.3"
http = "1"
http-body-util = "0.1"
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.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"
parking_lot = "0.12"
serde_json = "1"
tokio = { version = "1", default-features = false }
tower = "0.4"
tower-http = "0.5"
tracing = "0.1"
[dev-dependencies]
axum = "0.7.5"
tokio = { version = "1.39", features = ["net", "rt-multi-thread"] }
axum = "0.7"
tokio = { version = "1", features = ["net"] }
[features]
wasm = []

View File

@@ -34,22 +34,16 @@
use axum::{
body::{Body, Bytes},
extract::{FromRequestParts, MatchedPath},
extract::{FromRef, FromRequestParts, MatchedPath, State},
http::{
header::{self, HeaderName, HeaderValue, ACCEPT, LOCATION, REFERER},
request::Parts,
HeaderMap, Method, Request, Response, StatusCode,
HeaderMap, Method, Request, Response, StatusCode, Uri,
},
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},
@@ -68,7 +62,6 @@ 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;
@@ -102,9 +95,7 @@ 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.
/// ```
/// use leptos::prelude::*;
///
/// ```rust,ignore
/// #[server]
/// pub async fn get_opts() -> Result<(), ServerFnError> {
/// let opts = expect_context::<leptos_axum::ResponseOptions>();
@@ -178,33 +169,9 @@ impl ExtendResponse for AxumResponse {
}
}
/// 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.
/// 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
pub fn redirect(path: &str) {
if let (Some(req), Some(res)) =
(use_context::<Parts>(), use_context::<ResponseOptions>())
@@ -261,7 +228,7 @@ pub fn generate_request_and_parts(
///
/// ```
/// use axum::{handler::Handler, routing::post, Router};
/// use leptos::prelude::*;
/// use leptos::*;
/// use std::net::SocketAddr;
///
/// # if false { // don't actually try to run a server in a doctest...
@@ -345,13 +312,10 @@ 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, method)
{
if let Some(mut service) = server_fn::axum::get_server_fn_service(&path) {
let owner = Owner::new();
owner
.with(|| {
@@ -424,7 +388,8 @@ pub type PinnedHtmlStream =
/// This can then be set up at an appropriate route in your application:
/// ```
/// use axum::{handler::Handler, Router};
/// use leptos::{config::get_configuration, prelude::*};
/// use leptos::*;
/// use leptos_config::get_configuration;
/// use std::{env, net::SocketAddr};
///
/// #[component]
@@ -442,7 +407,8 @@ pub type PinnedHtmlStream =
///
/// // build our application with a route
/// let app = Router::new().fallback(leptos_axum::render_app_to_stream(
/// || { /* your application here */ },
/// leptos_options,
/// || view! { <MyApp/> },
/// ));
///
/// // run our app with hyper
@@ -510,7 +476,8 @@ where
/// This can then be set up at an appropriate route in your application:
/// ```
/// use axum::{handler::Handler, Router};
/// use leptos::{config::get_configuration, prelude::*};
/// use leptos::*;
/// use leptos_config::get_configuration;
/// use std::{env, net::SocketAddr};
///
/// #[component]
@@ -527,9 +494,11 @@ 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(|| view! { <MyApp/> }),
/// );
/// let app =
/// Router::new().fallback(leptos_axum::render_app_to_stream_in_order(
/// leptos_options,
/// || view! { <MyApp/> },
/// ));
///
/// // run our app with hyper
/// let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
@@ -567,25 +536,14 @@ 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
/// ```
/// 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 */ },
/// );
/// ```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/> }
/// );
/// handler(req).await.into_response()
/// }
/// ```
@@ -736,25 +694,14 @@ 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
/// ```
/// 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 */ },
/// );
/// ```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/> }
/// );
/// handler(req).await.into_response()
/// }
/// ```
@@ -887,7 +834,8 @@ fn provide_contexts(
/// This can then be set up at an appropriate route in your application:
/// ```
/// use axum::{handler::Handler, Router};
/// use leptos::{config::get_configuration, prelude::*};
/// use leptos::*;
/// use leptos_config::get_configuration;
/// use std::{env, net::SocketAddr};
///
/// #[component]
@@ -904,8 +852,10 @@ 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(|| view! { <MyApp/> }));
/// let app = Router::new().fallback(leptos_axum::render_app_async(
/// leptos_options,
/// || view! { <MyApp/> },
/// ));
///
/// // run our app with hyper
/// // `axum::Server` is a re-export of `hyper::Server`
@@ -946,25 +896,14 @@ 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
/// ```
/// 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 */ },
/// );
/// ```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/> }
/// );
/// handler(req).await.into_response()
/// }
/// ```
@@ -1011,25 +950,14 @@ 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
/// ```
/// 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 */ },
/// );
/// ```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/> }
/// );
/// handler(req).await.into_response()
/// }
/// ```
@@ -1238,7 +1166,7 @@ where
{
init_executor();
let owner = Owner::new_root(Some(Arc::new(SsrSharedContext::new())));
let owner = Owner::new_root(None);
let routes = owner
.with(|| {
// stub out a path for now
@@ -1748,19 +1676,15 @@ where
///
/// Any error that occurs during extraction is converted to a [`ServerFnError`].
///
/// ```rust
/// use leptos::prelude::*;
///
/// ```rust,ignore
/// // MyQuery is some type that implements `Deserialize + Serialize`
/// #[server]
/// pub async fn request_method() -> Result<String, ServerFnError> {
/// use http::Method;
/// use leptos_axum::extract;
/// pub async fn query_extract() -> Result<MyQuery, ServerFnError> {
/// use axum::{extract::Query, http::Method};
/// use leptos_axum::*;
/// let Query(query) = extract().await?;
///
/// // 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:?}"))
/// Ok(query)
/// }
/// ```
pub async fn extract<T>() -> Result<T, ServerFnError>
@@ -1778,6 +1702,18 @@ 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>,

View File

@@ -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.30"
futures = "0.3"
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.40"
tracing = "0.1.37"
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -1,62 +1,57 @@
[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.1", optional = true }
cfg-if = "1.0"
base64 = { version = "0.22", optional = true }
cfg-if = "1"
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.2.0", optional = true }
leptos-spin-macro = { version = "0.1", optional = true }
oco_ref = { workspace = true }
or_poisoned = { workspace = true }
paste = "1.0"
rand = { version = "0.8.5", optional = true }
paste = "1"
rand = { version = "0.8", optional = true }
reactive_graph = { workspace = true, features = ["serde"] }
rustc-hash = "2.0"
rustc-hash = "1"
tachys = { workspace = true, features = ["reactive_graph", "oco"] }
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 }
thiserror = "1"
tracing = "0.1"
typed-builder = "0.18"
typed-builder-macro = "0.18"
serde = "1"
serde_json = { version = "1", optional = true }
server_fn = { workspace = true, features = [
"form-redirects",
"browser",
"url",
] }
web-sys = { version = "0.3.70", features = [
web-sys = { version = "0.3.63", features = [
"ShadowRoot",
"ShadowRootInit",
"ShadowRootMode",
] }
wasm-bindgen = "0.2.93"
serde_qs = "0.13.0"
slotmap = "1.0"
wasm-bindgen = "0.2"
serde_qs = "0.12.0"
slotmap = "1.0.7"
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",
@@ -73,16 +68,23 @@ ssr = [
"hydration",
"tachys/ssr",
]
nightly = ["leptos_macro/nightly", "reactive_graph/nightly", "tachys/nightly"]
nightly = ["leptos_dom/nightly", "leptos_macro/nightly", "tachys/nightly"]
rkyv = ["server_fn/rkyv"]
tracing = [
"reactive_graph/tracing",
"tachys/tracing",
] #, "leptos_macro/tracing", "leptos_dom/tracing"]
nonce = ["base64", "rand"]
nonce = ["base64", "leptos_dom/nonce", "rand"]
spin = ["leptos-spin-macro"]
experimental-islands = ["leptos_macro/experimental-islands", "dep:serde_json"]
trace-component-props = ["leptos_macro/trace-component-props"]
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",
]
delegation = ["tachys/delegation"]
[package.metadata.cargo-all-features]

View File

@@ -6,17 +6,16 @@
//! 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::prelude::*;
//!
//! # use leptos::*;
//! #[component]
//! fn MyComponent(
//! #[prop(into)] render_number: Callback<i32, String>,
//! ) -> impl IntoView {
//! view! {
//! <div>
//! {render_number.run(1)}
//! {render_number.call(1)}
//! // callbacks can be called multiple times
//! {render_number.run(42)}
//! {render_number.call(42)}
//! </div>
//! }
//! }
@@ -31,28 +30,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.run(input)`.
//! - All callbacks implement the [`Callable`] trait, and can be invoked with `my_callback.call(input)`. On nightly, you can even do `my_callback(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`]
//! - [`UnsyncCallback`]
//! - [`SyncCallback`]
//!
//! Use `SyncCallback` if the function is not `Sync` and `Send`.
//! Use `SyncCallback` when you want the function to be `Sync` and `Send`.
use reactive_graph::owner::{LocalStorage, StoredValue};
use reactive_graph::owner::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 run(&self, input: In) -> Out;
fn call(&self, input: In) -> Out;
}
/// A callback type that is not required to be `Send + Sync`.
pub struct UnsyncCallback<In: 'static, Out: 'static = ()>(
StoredValue<Rc<dyn Fn(In) -> Out>, LocalStorage>,
Rc<dyn Fn(In) -> Out>,
);
impl<In> fmt::Debug for UnsyncCallback<In> {
@@ -61,11 +60,9 @@ 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
Self(Rc::clone(&self.0))
}
}
@@ -75,40 +72,92 @@ impl<In, Out> UnsyncCallback<In, Out> {
where
F: Fn(In) -> Out + 'static,
{
Self(StoredValue::new_local(Rc::new(f)))
Self(Rc::new(f))
}
}
impl<In: 'static, Out: 'static> Callable<In, Out> for UnsyncCallback<In, Out> {
fn run(&self, input: In) -> Out {
self.0.with_value(|fun| fun(input))
fn call(&self, input: In) -> Out {
(self.0)(input)
}
}
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())
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)
}
}
#[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::prelude::*;
/// # use leptos::callback::{Callable, Callback};
/// # use leptos::*;
/// # use leptos::{Callable, Callback};
/// #[component]
/// fn MyComponent(
/// #[prop(into)] render_number: Callback<i32, String>,
/// ) -> impl IntoView {
/// view! {
/// <div>
/// {render_number.run(42)}
/// {render_number.call(42)}
/// </div>
/// }
/// }
@@ -133,7 +182,7 @@ impl<In, Out> fmt::Debug for Callback<In, Out> {
}
impl<In, Out> Callable<In, Out> for Callback<In, Out> {
fn run(&self, input: In) -> Out {
fn call(&self, input: In) -> Out {
self.0
.try_with_value(|f| f(input))
.expect("called a callback that has been disposed")
@@ -148,17 +197,6 @@ 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
@@ -169,32 +207,114 @@ 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};
use crate::{
callback::{Callback, UnsyncCallback},
create_runtime,
};
struct NoClone {}
#[test]
fn clone_callback() {
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
let _cloned = callback.clone();
}
#[test]
fn clone_unsync_callback() {
let rt = create_runtime();
let callback =
UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
let _cloned = callback.clone();
rt.dispose();
}
#[test]
fn runback_from() {
let _callback: Callback<(), String> = (|()| "test").into();
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();
}
#[test]
fn sync_callback_from() {
let _callback: UnsyncCallback<(), String> = (|()| "test").into();
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();
}
}

View File

@@ -47,44 +47,70 @@ 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
///
/// 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
///
/// ## Examples
/// 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].
///
/// ```
/// # use leptos::prelude::*;
/// # use leptos::html::p;
/// # use leptos::IntoView;
/// # use leptos::{ProviderProps, ShowProps};
/// # use leptos_dom::html::p;
/// # use leptos_dom::IntoView;
/// # use leptos_macro::component;
/// # use leptos::children::ToChildren;
/// use leptos::context::{Provider, ProviderProps};
/// use leptos::control_flow::{Show, ShowProps};
///
/// # 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;
/// #
/// #[component]
/// fn App() -> impl IntoView {
/// (
/// Provider(
/// ProviderProps::builder()
/// .children(ToChildren::to_children(|| {
/// p().child("Foo")
/// p().child("Foo").into_view().into()
/// }))
/// // ...
/// .value("Foo")
/// .build(),
/// ),
/// Show(
/// ShowProps::builder()
/// # .value("Foo")
/// # .build(),
/// ShowProps::builder()
/// .children(ToChildren::to_children(|| {
/// p().child("Foo")
/// p().child("Foo").into_view().into()
/// }))
/// // ...
/// .when(|| true)
/// .fallback(|| p().child("foo"))
/// .build(),
/// )
/// # .when(|| true)
/// # .fallback(|| p().child("foo"))
/// # .build(),
/// )
/// }
pub trait ToChildren<F> {

View File

@@ -9,7 +9,7 @@ use reactive_graph::{
traits::{Get, Update, With, WithUntracked},
};
use rustc_hash::FxHashMap;
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
use std::{marker::PhantomData, sync::Arc};
use tachys::{
html::attribute::Attribute,
hydration::Cursor,
@@ -22,29 +22,6 @@ 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).
@@ -95,11 +72,12 @@ where
});
let hook = hook as Arc<dyn ErrorHook>;
let _guard = throw_error::set_error_hook(Arc::clone(&hook));
// provide the error hook and render children
// TODO unset this outside the ErrorBoundary
throw_error::set_error_hook(Arc::clone(&hook));
let children = children.into_inner()();
ErrorBoundaryView {
hook,
boundary_id,
errors_empty,
children,
@@ -109,8 +87,8 @@ where
}
}
#[derive(Debug)]
struct ErrorBoundaryView<Chil, FalFn, Rndr> {
hook: Arc<dyn ErrorHook>,
boundary_id: SerializedDataId,
errors_empty: ArcMemo<bool>,
children: Chil,
@@ -167,14 +145,11 @@ 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
@@ -241,7 +216,6 @@ where
Self::Output<NewAttr>: RenderHtml<Rndr>,
{
let ErrorBoundaryView {
hook,
boundary_id,
errors_empty,
children,
@@ -250,7 +224,6 @@ where
rndr,
} = self;
ErrorBoundaryView {
hook,
boundary_id,
errors_empty,
children: children.add_any_attr(attr.into_cloneable_owned()),
@@ -279,7 +252,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
let ErrorBoundaryView {
hook,
boundary_id,
errors_empty,
children,
@@ -288,7 +260,6 @@ where
..
} = self;
ErrorBoundaryView {
hook,
boundary_id,
errors_empty,
children: children.resolve().await,
@@ -306,7 +277,6 @@ 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(
@@ -339,7 +309,6 @@ 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;
@@ -350,6 +319,10 @@ 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);
@@ -372,14 +345,12 @@ 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
@@ -453,10 +424,7 @@ impl ErrorBoundaryErrorHook {
impl ErrorHook for ErrorBoundaryErrorHook {
fn throw(&self, error: Error) -> ErrorId {
// generate a unique ID
let key: ErrorId = Owner::current_shared_context()
.map(|sc| sc.next_id())
.unwrap_or_default()
.into();
let key = ErrorId::default(); // TODO unique ID...
// register it with the shared context, so that it can be serialized from server to client
// as needed

View File

@@ -1,10 +1,6 @@
use crate::into_view::IntoView;
use leptos_macro::component;
use reactive_graph::{
owner::Owner,
signal::{ArcRwSignal, ReadSignal},
traits::Set,
};
use reactive_graph::owner::Owner;
use std::hash::Hash;
use tachys::{reactive_graph::OwnedView, view::keyed::keyed};
@@ -14,7 +10,7 @@ use tachys::{reactive_graph::OwnedView, view::keyed::keyed};
/// as it avoids re-creating DOM nodes that are not being changed.
///
/// ```
/// # use leptos::prelude::*;
/// # use leptos::*;
///
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// struct Counter {
@@ -72,88 +68,10 @@ 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))
};
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),
)
OwnedView::new_with_owner(view, owner)
};
move || keyed(each(), key.clone(), children.clone())
}
@@ -162,57 +80,25 @@ where
mod tests {
use crate::prelude::*;
use leptos_macro::view;
use tachys::{html::element::HtmlElement, prelude::ElementChild};
use tachys::{
html::element::HtmlElement, prelude::ElementChild,
renderer::mock_dom::MockDom, view::Render,
};
#[test]
fn creates_list() {
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>"
);
});
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>"
);
}
}

View File

@@ -34,8 +34,8 @@ use web_sys::{
/// should make use of indexing notation of `serde_qs`.
///
/// ```rust
/// # use leptos::prelude::*;
/// use leptos::form::ActionForm;
/// # use leptos::*;
/// # use leptos_router::*;
///
/// #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
/// struct HeftyData {
@@ -45,7 +45,7 @@ use web_sys::{
///
/// #[component]
/// fn ComplexInput() -> impl IntoView {
/// let submit = ServerAction::<VeryImportantFn>::new();
/// let submit = Action::<VeryImportantFn, _>::server();
///
/// view! {
/// <ActionForm action=submit>
@@ -145,6 +145,10 @@ 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
@@ -212,6 +216,10 @@ 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.

View File

@@ -24,13 +24,12 @@ pub fn AutoReload(
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
};
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> }
let script = include_str!("reload_script.js");
view! {
<script nonce=nonce>
{format!("{script}({reload_port:?}, {protocol})")}
</script>
}
})
}
@@ -50,9 +49,6 @@ 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")
@@ -60,13 +56,7 @@ 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>

View File

@@ -1,3 +1,4 @@
(function (reload_port, protocol) {
let host = window.location.hostname;
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
ws.onmessage = (ev) => {
@@ -19,3 +20,4 @@ ws.onmessage = (ev) => {
}
};
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
})

View File

@@ -1,50 +1,18 @@
use std::borrow::Cow;
use tachys::{
html::attribute::Attribute,
hydration::Cursor,
renderer::{dom::Dom, Renderer},
renderer::dom::Dom,
ssr::StreamBuilder,
view::{
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
ToTemplate,
},
view::{add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct View<T>
pub struct View<T>(T)
where
T: Sized,
{
inner: T,
#[cfg(debug_assertions)]
view_marker: Option<Cow<'static, str>>,
}
T: Sized;
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.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
self.0
}
}
@@ -60,37 +28,33 @@ where
T: Sized + Render<Dom> + RenderHtml<Dom> + Send, //+ AddAnyAttr<Dom>,
{
fn into_view(self) -> View<Self> {
View {
inner: self,
#[cfg(debug_assertions)]
view_marker: None,
}
View(self)
}
}
impl<T: Render<Rndr>, Rndr: Renderer> Render<Rndr> for View<T> {
impl<T: IntoView> Render<Dom> for View<T> {
type State = T::State;
fn build(self) -> Self::State {
self.inner.build()
self.0.build()
}
fn rebuild(self, state: &mut Self::State) {
self.inner.rebuild(state)
self.0.rebuild(state)
}
}
impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
impl<T: IntoView> RenderHtml<Dom> for View<T> {
type AsyncOutput = T::AsyncOutput;
const MIN_LENGTH: usize = <T as RenderHtml<Rndr>>::MIN_LENGTH;
const MIN_LENGTH: usize = <T as RenderHtml<Dom>>::MIN_LENGTH;
async fn resolve(self) -> Self::AsyncOutput {
self.inner.resolve().await
self.0.resolve().await
}
fn dry_resolve(&mut self) {
self.inner.dry_resolve();
self.0.dry_resolve();
}
fn to_html_with_buf(
@@ -100,20 +64,8 @@ impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
escape: bool,
mark_branches: bool,
) {
#[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
self.0
.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>(
@@ -125,70 +77,35 @@ impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
) where
Self: Sized,
{
#[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>(
self.0.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<Rndr>,
cursor: &Cursor<Dom>,
position: &PositionState,
) -> Self::State {
self.inner.hydrate::<FROM_SERVER>(cursor, position)
self.0.hydrate::<FROM_SERVER>(cursor, position)
}
}
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);
}
}
impl<T: IntoView> AddAnyAttr<Dom> for View<T> {
type Output<SomeNewAttr: Attribute<Dom>> =
<T as AddAnyAttr<Dom>>::Output<SomeNewAttr>;
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>>(
fn add_any_attr<NewAttr: Attribute<Dom>>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml<Rndr>,
Self::Output<NewAttr>: RenderHtml<Dom>,
{
let View {
inner,
#[cfg(debug_assertions)]
view_marker,
} = self;
View {
inner: inner.add_any_attr(attr),
#[cfg(debug_assertions)]
view_marker,
}
self.0.add_any_attr(attr)
}
}

View File

@@ -1,4 +1,4 @@
#!rdeny(missing_docs)]
//#!rdeny(missing_docs)] // TODO restore
#![forbid(unsafe_code)]
//! # About Leptos
//!
@@ -98,25 +98,25 @@
//! # A Simple Counter
//!
//! ```rust
//! use leptos::prelude::*;
//! use leptos::*;
//!
//! #[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) = signal( initial_value);
//! let (value, set_value) = create_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.write() -= 1;
//! let increment = move |_| *set_value.write() += 1;
//! let decrement = move |_| set_value.update(|value| *value -= 1);
//! let increment = move |_| set_value.update(|value| *value += 1);
//!
//! view! {
//!
//! <div>
//! <button on:click=clear>"Clear"</button>
//! <button on:click=decrement>"-1"</button>
//! <span>"Value: " {value} "!"</span>
//! <span>"Value: " {move || value.get().to_string()} "!"</span>
//! <button on:click=increment>"+1"</button>
//! </div>
//! }
@@ -125,23 +125,24 @@
//!
//! Leptos is easy to use with [Trunk](https://trunkrs.dev/) (or with a simple wasm-bindgen setup):
//! ```
//! use leptos::{mount::mount_to_body, prelude::*};
//! # use leptos::*;
//! # if false { // can't run in doctests
//!
//! #[component]
//! fn SimpleCounter(initial_value: i32) -> impl IntoView {
//! // ...
//! # _ = initial_value;
//! todo!()
//! }
//!
//! 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;
@@ -295,17 +296,10 @@ pub mod spawn {
}
}
// these reexports are used in islands
#[cfg(feature = "experimental-islands")]
#[doc(hidden)]
pub use serde;
#[cfg(feature = "experimental-islands")]
pub use wasm_bindgen; // used in islands
#[doc(hidden)]
pub use serde_json;
#[doc(hidden)]
pub use wasm_bindgen;
#[doc(hidden)]
pub use web_sys;
pub use web_sys; // used in islands
/*mod additional_attributes;
pub use additional_attributes::*;

View File

@@ -1,10 +1,6 @@
#[cfg(debug_assertions)]
use crate::logging;
use crate::IntoView;
use crate::{logging, 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,
@@ -31,11 +27,6 @@ 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>(
@@ -54,19 +45,15 @@ where
// already initialized, which is not an issue
_ = Executor::init_wasm_bindgen();
#[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);
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."
);
}
// create a new reactive owner and use it as the root node to run the app
@@ -113,20 +100,16 @@ where
// already initialized, which is not an issue
_ = Executor::init_wasm_bindgen();
#[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);
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."
);
}
// create a new reactive owner and use it as the root node to run the app
@@ -183,7 +166,7 @@ where
/// Hydrates any islands that are currently present on the page.
#[cfg(feature = "hydrate")]
pub fn hydrate_islands() {
use hydration_context::{HydrateSharedContext, SharedContext};
use hydration_context::HydrateSharedContext;
use std::sync::Arc;
// use wasm-bindgen-futures to drive the reactive system
@@ -191,13 +174,8 @@ 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 sc = HydrateSharedContext::new();
sc.set_is_hydrating(false); // islands mode starts in "not hydrating"
let owner = Owner::new_root(Some(Arc::new(sc)));
let owner = Owner::new_root(Some(Arc::new(HydrateSharedContext::new())));
owner.set();
std::mem::forget(owner);
}

View File

@@ -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, untrack};
use reactive_graph::{effect::Effect, owner::Owner};
use std::sync::Arc;
/// Renders components somewhere else in the DOM.
@@ -62,11 +62,12 @@ 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 || untrack(|| children())
move || children()
}),
mount.clone(),
container,

View File

@@ -9,8 +9,7 @@ use tachys::reactive_graph::OwnedView;
/// This prevents issues related to “context shadowing.”
///
/// ```rust
/// use leptos::{context::Provider, prelude::*};
///
/// # use leptos::prelude::*;
/// #[component]
/// pub fn App() -> impl IntoView {
/// // each Provider will only provide the value to its children

View File

@@ -31,61 +31,6 @@ 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,
@@ -442,101 +387,3 @@ 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)
}
}

View File

@@ -15,61 +15,7 @@ use reactive_graph::{
use slotmap::{DefaultKey, SlotMap};
use tachys::reactive_graph::OwnedView;
/// 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>
/// }
/// # ;}
/// ```
/// TODO docs!
#[component]
pub fn Transition<Chil>(
/// Will be displayed while resources are pending. By default this is the empty view.

View File

@@ -1,11 +1,11 @@
use leptos::html::HtmlElement;
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
#[test]
fn simple_ssr_test() {
use leptos::prelude::*;
let runtime = create_runtime();
let (value, set_value) = signal(0);
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
let rendered = view! {
<div>
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
<span>"Value: " {move || value.get().to_string()} "!"</span>
@@ -13,13 +13,26 @@ fn simple_ssr_test() {
</div>
};
assert_eq!(
rendered.to_html(),
"<div><button>-1</button><span>Value: \
<!>0<!>!</span><button>+1</button></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();
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
#[test]
fn ssr_test_with_components() {
use leptos::prelude::*;
@@ -36,21 +49,35 @@ fn ssr_test_with_components() {
}
}
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
let runtime = create_runtime();
let rendered = view! {
<div class="counters">
<Counter initial_value=1/>
<Counter initial_value=2/>
</div>
};
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>"
);
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();
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
#[test]
fn ssr_test_with_snake_case_components() {
use leptos::prelude::*;
@@ -59,6 +86,7 @@ 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>
@@ -66,65 +94,111 @@ fn ssr_test_with_snake_case_components() {
</div>
}
}
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
let runtime = create_runtime();
let rendered = view! {
<div class="counters">
<SnakeCaseCounter initial_value=1/>
<SnakeCaseCounter initial_value=2/>
</div>
};
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>"
);
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();
}
#[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<HtmlElement<_, _, _, Dom>> = view! {
<div
class="my big"
class:a=move || { value.get() > 10 }
class:red=true
class:car=move || { value.get() > 1 }
></div>
let rendered = view! {
<div class="my big" class:a={move || value.get() > 10} class:red=true class:car={move || value.get() > 1}></div>
};
assert_eq!(rendered.to_html(), "<div class=\"my big red car\"></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();
}
#[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<HtmlElement<_, _, _, Dom>> = view! { class=styles,
let rendered = 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>
};
assert_eq!(
rendered.to_html(),
"<div class=\"myclass\"><button class=\"btn \
myclass\">-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();
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
#[test]
fn ssr_option() {
use leptos::prelude::*;
let runtime = create_runtime();
let (_, _) = signal(0);
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { <option></option> };
let rendered = view! {
assert_eq!(rendered.to_html(), "<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();
}

View File

@@ -1,27 +1,24 @@
[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.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"
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"
[dev-dependencies]
tokio = { version = "1.39", features = ["rt", "macros"] }
tempfile = "3.12"
tokio = { version = "1", features = ["rt", "macros"] }
tempfile = "3"
temp-env = { version = "0.3.6", features = ["async_closure"] }
[package.metadata.docs.rs]

View File

@@ -1,31 +1,40 @@
[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 }
js-sys = "0.3.69"
send_wrapper = "0.6.0"
tracing = "0.1.40"
wasm-bindgen = "0.2.93"
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"
[dev-dependencies]
leptos = { path = "../leptos" }
[dependencies.web-sys]
version = "0.3.70"
version = "0.3"
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]

View File

@@ -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.0", optional = true }
wasm-bindgen = { version = "0.2.0", optional = true }
gloo = { version = "0.11.0", optional = true }
actix-files = { version = "0.6", optional = true }
wasm-bindgen = { version = "0.2", optional = true }
gloo = { version = "0.8", optional = true }
console_error_panic_hook = "0.1.7"
futures = "0.3.0"
futures = "0.3"
[features]
default = ["ssr"]

View File

@@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2021"
[dependencies]
console_error_panic_hook = "0.1.0"
gloo = { version = "0.11.0", features = ["futures"] }
console_error_panic_hook = "0.1"
gloo = { version = "0.8", features = ["futures"] }
leptos = { path = "../../../leptos", features = ["nightly", "csr", "tracing"] }
tracing = "0.1.0"
tracing-subscriber = "0.3.0"
tracing-subscriber-wasm = "0.1.0"
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-subscriber-wasm = "0.1"
[workspace]

View File

@@ -5,9 +5,10 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
console_log = "1.0"
log = "0.4.0"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View File

@@ -0,0 +1,287 @@
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()
}
}

View File

@@ -0,0 +1,452 @@
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

View File

@@ -0,0 +1,166 @@
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())
}
}

View File

@@ -0,0 +1,117 @@
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()
}
}

View File

@@ -0,0 +1,73 @@
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))
}
}

View File

@@ -0,0 +1,83 @@
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