Compare commits

..

3 Commits

Author SHA1 Message Date
Greg Johnston
45c285d9ee v0.1.2 2025-07-26 14:06:13 -04:00
Greg Johnston
7d60eb1b60 v0.1.2 2025-07-26 14:04:55 -04:00
Greg Johnston
6b200b6224 fix: support file hashing when using lazy loading 2025-07-26 10:43:23 -04:00
101 changed files with 5686 additions and 1762 deletions

View File

@@ -18,7 +18,7 @@ jobs:
autofix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with: {toolchain: "nightly-2025-07-16", components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
- name: Install Glib

View File

@@ -63,6 +63,6 @@ jobs:
sudo apt-get update
sudo apt-get install -y libglib2.0-dev
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Semver Checks
uses: obi1kenobi/cargo-semver-checks-action@v2

View File

@@ -19,7 +19,7 @@ jobs:
matrix: ${{ steps.set-example-changed.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get example files that changed

View File

@@ -17,7 +17,7 @@ jobs:
EXCLUDED_EXAMPLES: cargo-make
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install jq
run: sudo apt-get install jq
- name: Set Matrix

View File

@@ -13,7 +13,7 @@ jobs:
leptos_changed: ${{ steps.set-source-changed.outputs.leptos_changed }}
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get source files that changed

View File

@@ -13,7 +13,7 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install jq
run: sudo apt-get install jq
- name: Set Matrix

View File

@@ -12,7 +12,7 @@ jobs:
contents: write # To push a branch
pull-requests: write # To create a PR from that branch
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install mdbook

View File

@@ -53,7 +53,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libglib2.0-dev
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:

453
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,7 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.8.5"
edition = "2021"
rust-version = "1.88"
@@ -50,39 +51,39 @@ any_spawner = { path = "./any_spawner/", version = "0.3.0" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.6" }
hydration_context = { path = "./hydration_context", version = "0.3.0" }
leptos = { path = "./leptos", version = "0.8.9" }
leptos_config = { path = "./leptos_config", version = "0.8.7" }
leptos_dom = { path = "./leptos_dom", version = "0.8.6" }
leptos = { path = "./leptos", version = "0.8.5" }
leptos_config = { path = "./leptos_config", version = "0.8.5" }
leptos_dom = { path = "./leptos_dom", version = "0.8.5" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.5" }
leptos_macro = { path = "./leptos_macro", version = "0.8.8" }
leptos_router = { path = "./router", version = "0.8.7" }
leptos_macro = { path = "./leptos_macro", version = "0.8.5" }
leptos_router = { path = "./router", version = "0.8.5" }
leptos_router_macro = { path = "./router_macro", version = "0.8.5" }
leptos_server = { path = "./leptos_server", version = "0.8.5" }
leptos_meta = { path = "./meta", version = "0.8.5" }
next_tuple = { path = "./next_tuple", version = "0.1.0" }
oco_ref = { path = "./oco", version = "0.2.1" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.2.7" }
reactive_graph = { path = "./reactive_graph", version = "0.2.5" }
reactive_stores = { path = "./reactive_stores", version = "0.2.5" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.6" }
server_fn = { path = "./server_fn", version = "0.8.7" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.7" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.5" }
server_fn = { path = "./server_fn", version = "0.8.5" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.5" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.5" }
tachys = { path = "./tachys", version = "0.2.8" }
wasm_split_helpers = { path = "./wasm_split", version = "0.1.2" }
wasm_split_macros = { path = "./wasm_split_macros", version = "0.1.3" }
tachys = { path = "./tachys", version = "0.2.6" }
wasm_split_helpers = { path = "./wasm_split", version = "0.1.1" }
wasm_split_macros = { path = "./wasm_split_macros", version = "0.1.1" }
# members deps
async-once-cell = { default-features = false, version = "0.5.3" }
itertools = { default-features = false, version = "0.14.0" }
convert_case = { default-features = false, version = "0.8.0" }
serde_json = { default-features = false, version = "1.0.143" }
trybuild = { default-features = false, version = "1.0.110" }
typed-builder = { default-features = false, version = "0.21.2" }
thiserror = { default-features = false, version = "2.0.16" }
serde_json = { default-features = false, version = "1.0.140" }
trybuild = { default-features = false, version = "1.0.106" }
typed-builder = { default-features = false, version = "0.21.0" }
thiserror = { default-features = false, version = "2.0.12" }
wasm-bindgen = { default-features = false, version = "0.2.100" }
indexmap = { default-features = false, version = "2.11.0" }
indexmap = { default-features = false, version = "2.9.0" }
rstml = { default-features = false, version = "0.12.1" }
rustc_version = { default-features = false, version = "0.4.1" }
guardian = { default-features = false, version = "1.3.0" }
@@ -100,17 +101,17 @@ proc-macro-error2 = { default-features = false, version = "2.0.1" }
const_format = { default-features = false, version = "0.2.34" }
gloo-net = { default-features = false, version = "0.6.0" }
url = { default-features = false, version = "2.5.4" }
tokio = { default-features = false, version = "1.47.1" }
tokio = { default-features = false, version = "1.46.1" }
base64 = { default-features = false, version = "0.22.1" }
cfg-if = { default-features = false, version = "1.0.3" }
cfg-if = { default-features = false, version = "1.0.0" }
wasm-bindgen-futures = { default-features = false, version = "0.4.50" }
tower = { default-features = false, version = "0.5.2" }
proc-macro2 = { default-features = false, version = "1.0.101" }
proc-macro2 = { default-features = false, version = "1.0.95" }
serde = { default-features = false, version = "1.0.219" }
parking_lot = { default-features = false, version = "0.12.4" }
axum = { default-features = false, version = "0.8.4" }
serde_qs = { default-features = false, version = "0.15.0" }
syn = { default-features = false, version = "2.0.106" }
syn = { default-features = false, version = "2.0.104" }
xxhash-rust = { default-features = false, version = "0.8.15" }
paste = { default-features = false, version = "1.0.15" }
quote = { default-features = false, version = "1.0.40" }
@@ -122,51 +123,51 @@ tokio-tungstenite = { default-features = false, version = "0.27.0" }
serial_test = { default-features = false, version = "3.2.0" }
erased = { default-features = false, version = "0.1.2" }
glib = { default-features = false, version = "0.20.12" }
async-trait = { default-features = false, version = "0.1.89" }
async-trait = { default-features = false, version = "0.1.88" }
typed-builder-macro = { default-features = false, version = "0.21.0" }
linear-map = { default-features = false, version = "1.2.0" }
anyhow = { default-features = false, version = "1.0.99" }
anyhow = { default-features = false, version = "1.0.98" }
walkdir = { default-features = false, version = "2.5.0" }
actix-ws = { default-features = false, version = "0.3.0" }
tower-http = { default-features = false, version = "0.6.4" }
prettyplease = { default-features = false, version = "0.2.37" }
inventory = { default-features = false, version = "0.3.21" }
config = { default-features = false, version = "0.15.14" }
camino = { default-features = false, version = "1.1.11" }
prettyplease = { default-features = false, version = "0.2.35" }
inventory = { default-features = false, version = "0.3.20" }
config = { default-features = false, version = "0.15.13" }
camino = { default-features = false, version = "1.1.9" }
ciborium = { default-features = false, version = "0.2.2" }
multer = { default-features = false, version = "3.1.0" }
leptos-spin-macro = { default-features = false, version = "0.2.0" }
sledgehammer_utils = { default-features = false, version = "0.3.1" }
sledgehammer_bindgen = { default-features = false, version = "0.6.0" }
wasm-streams = { default-features = false, version = "0.4.2" }
rkyv = { default-features = false, version = "0.8.11" }
rkyv = { default-features = false, version = "0.8.10" }
temp-env = { default-features = false, version = "0.3.6" }
uuid = { default-features = false, version = "1.18.0" }
uuid = { default-features = false, version = "1.17.0" }
bytes = { default-features = false, version = "1.10.1" }
http = { default-features = false, version = "1.3.1" }
regex = { default-features = false, version = "1.11.2" }
regex = { default-features = false, version = "1.11.1" }
drain_filter_polyfill = { default-features = false, version = "0.1.3" }
tempfile = { default-features = false, version = "3.21.0" }
futures-lite = { default-features = false, version = "2.6.1" }
tempfile = { default-features = false, version = "3.20.0" }
futures-lite = { default-features = false, version = "2.6.0" }
log = { default-features = false, version = "0.4.27" }
percent-encoding = { default-features = false, version = "2.3.2" }
percent-encoding = { default-features = false, version = "2.3.1" }
async-executor = { default-features = false, version = "1.13.2" }
const-str = { default-features = false, version = "0.6.4" }
const-str = { default-features = false, version = "0.6.3" }
http-body-util = { default-features = false, version = "0.1.3" }
hyper = { default-features = false, version = "1.7.0" }
postcard = { default-features = false, version = "1.1.3" }
hyper = { default-features = false, version = "1.6.0" }
postcard = { default-features = false, version = "1.1.1" }
rmp-serde = { default-features = false, version = "1.3.0" }
reqwest = { default-features = false, version = "0.12.23" }
reqwest = { default-features = false, version = "0.12.22" }
tower-layer = { default-features = false, version = "0.3.3" }
attribute-derive = { default-features = false, version = "0.10.3" }
insta = { default-features = false, version = "1.43.1" }
codee = { default-features = false, version = "0.3.0" }
actix-http = { default-features = false, version = "3.11.1" }
actix-http = { default-features = false, version = "3.11.0" }
wasm-bindgen-test = { default-features = false, version = "0.3.50" }
rustversion = { default-features = false, version = "1.0.22" }
rustversion = { default-features = false, version = "1.0.21" }
getrandom = { default-features = false, version = "0.3.3" }
actix-files = { default-features = false, version = "0.6.6" }
async-lock = { default-features = false, version = "3.4.1" }
async-lock = { default-features = false, version = "3.4.0" }
base16 = { default-features = false, version = "0.2.1" }
digest = { default-features = false, version = "0.10.7" }
sha2 = { default-features = false, version = "0.10.8" }

View File

@@ -1,7 +0,0 @@
@check_issue_4005
Feature: Check that issue 4005 does not reappear
Scenario: The second item is selected.
Given I see the app
And I can access regression test 4005
Then I see the value of select is 2

View File

@@ -1,9 +0,0 @@
@check_issue_4217
Feature: Check that issue 4217 does not reappear
Scenario: All items are selected.
Given I see the app
And I can access regression test 4217
Then I see option1 is selected
And I see option2 is selected
And I see option3 is selected

View File

@@ -1,9 +0,0 @@
@check_issue_4285
Feature: Check that issue 4285 does not reappear
Scenario: Navigating several times to same lazy route does not cause issues.
Given I see the app
And I can access regression test 4285
And I can access regression test 4285
And I can access regression test 4285
Then I see the result is the string 42

View File

@@ -1,18 +0,0 @@
@check_issue_4296
Feature: Check that issue 4296 does not reappear
Scenario: Query param signals created in LazyRoute::data() are reactive in ::view().
Given I see the app
And I can access regression test 4296
Then I see the result is the string None
When I select the link abc
Then I see the result is the string Some("abc")
When I select the link def
Then I see the result is the string Some("def")
Scenario: Loading page with query signal works as well.
Given I see the app
And I can access regression test 4296
When I select the link abc
When I reload the page
Then I see the result is the string Some("abc")

View File

@@ -18,28 +18,3 @@ pub async fn element_exists(client: &Client, id: &str) -> Result<()> {
.expect(&format!("could not find element with id `{id}`"));
Ok(())
}
pub async fn select_option_is_selected(
client: &Client,
id: &str,
) -> Result<()> {
let el = find::element_by_id(client, id)
.await
.expect(&format!("could not find element with id `{id}`"));
let selected = el.prop("selected").await?;
assert_eq!(selected.as_deref(), Some("true"));
Ok(())
}
pub async fn element_value_is(
client: &Client,
id: &str,
expected: &str,
) -> Result<()> {
let el = find::element_by_id(client, id)
.await
.expect(&format!("could not find element with id `{id}`"));
let value = el.prop("value").await?;
assert_eq!(value.as_deref(), Some(expected));
Ok(())
}

View File

@@ -25,21 +25,3 @@ async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
check::element_exists(client, "nav").await?;
Ok(())
}
#[then(regex = r"^I see ([\d\w]+) is selected$")]
async fn i_see_the_select(world: &mut AppWorld, id: String) -> Result<()> {
let client = &world.client;
check::select_option_is_selected(client, &id).await?;
Ok(())
}
#[then(regex = r"^I see the value of (\w+) is (.*)$")]
async fn i_see_the_value(
world: &mut AppWorld,
id: String,
value: String,
) -> Result<()> {
let client = &world.client;
check::element_value_is(client, &id, &value).await?;
Ok(())
}

View File

@@ -1,8 +1,4 @@
use crate::{
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
issue_4285::Routes4285, issue_4296::Routes4296, pr_4015::Routes4015,
pr_4091::Routes4091,
};
use crate::{issue_4088::Routes4088, pr_4015::Routes4015, pr_4091::Routes4091};
use leptos::prelude::*;
use leptos_meta::{MetaTags, *};
use leptos_router::{
@@ -32,21 +28,15 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
pub fn App() -> impl IntoView {
provide_meta_context();
let fallback = || view! { "Page not found." }.into_view();
let (_, set_is_routing) = signal(false);
view! {
<Stylesheet id="leptos" href="/pkg/regression.css"/>
<Router set_is_routing>
<Router>
<main>
<Routes fallback>
<Route path=path!("") view=HomePage/>
<Routes4091/>
<Routes4015/>
<Routes4088/>
<Routes4217/>
<Routes4005/>
<Routes4285/>
<Routes4296/>
</Routes>
</main>
</Router>
@@ -69,10 +59,6 @@ fn HomePage() -> impl IntoView {
<li><a href="/4091/">"4091"</a></li>
<li><a href="/4015/">"4015"</a></li>
<li><a href="/4088/">"4088"</a></li>
<li><a href="/4217/">"4217"</a></li>
<li><a href="/4005/">"4005"</a></li>
<li><a href="/4285/">"4285"</a></li>
<li><a href="/4296/">"4296"</a></li>
</ul>
</nav>
}

View File

@@ -1,24 +0,0 @@
use leptos::prelude::*;
#[allow(unused_imports)]
use leptos_router::{
components::Route, path, MatchNestedRoutes, NavigateOptions,
};
#[component]
pub fn Routes4005() -> impl MatchNestedRoutes + Clone {
view! {
<Route path=path!("4005") view=Issue4005/>
}
.into_inner()
}
#[component]
fn Issue4005() -> impl IntoView {
view! {
<select id="select" prop:value="2">
<option value="1">"Option 1"</option>
<option value="2">"Option 2"</option>
<option value="3">"Option 3"</option>
</select>
}
}

View File

@@ -1,24 +0,0 @@
use leptos::prelude::*;
#[allow(unused_imports)]
use leptos_router::{
components::Route, path, MatchNestedRoutes, NavigateOptions,
};
#[component]
pub fn Routes4217() -> impl MatchNestedRoutes + Clone {
view! {
<Route path=path!("4217") view=Issue4217/>
}
.into_inner()
}
#[component]
fn Issue4217() -> impl IntoView {
view! {
<select multiple=true>
<option id="option1" value="1" selected>"Option 1"</option>
<option id="option2" value="2" selected>"Option 2"</option>
<option id="option3" value="3" selected>"Option 3"</option>
</select>
}
}

View File

@@ -1,49 +0,0 @@
use leptos::prelude::*;
use leptos_router::LazyRoute;
#[allow(unused_imports)]
use leptos_router::{
components::Route, path, Lazy, MatchNestedRoutes, NavigateOptions,
};
#[component]
pub fn Routes4285() -> impl MatchNestedRoutes + Clone {
view! {
<Route path=path!("4285") view={Lazy::<Issue4285>::new()}/>
}
.into_inner()
}
struct Issue4285 {
data: Resource<Result<i32, ServerFnError>>,
}
impl LazyRoute for Issue4285 {
fn data() -> Self {
Self {
data: Resource::new(|| (), |_| slow_call()),
}
}
async fn view(this: Self) -> AnyView {
let Issue4285 { data } = this;
view! {
<Suspense>
{move || {
Suspend::new(async move {
let data = data.await;
view! {
<p id="result">{data}</p>
}
})
}}
</Suspense>
}
.into_any()
}
}
#[server]
async fn slow_call() -> Result<i32, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
Ok(42)
}

View File

@@ -1,36 +0,0 @@
use leptos::prelude::*;
#[allow(unused_imports)]
use leptos_router::{
components::Route, path, Lazy, MatchNestedRoutes, NavigateOptions,
};
use leptos_router::{hooks::use_query_map, LazyRoute};
#[component]
pub fn Routes4296() -> impl MatchNestedRoutes + Clone {
view! {
<Route path=path!("4296") view={Lazy::<Issue4296>::new()}/>
}
.into_inner()
}
struct Issue4296 {
query: Signal<Option<String>>,
}
impl LazyRoute for Issue4296 {
fn data() -> Self {
let query = use_query_map();
let query = Signal::derive(move || query.read().get("q"));
Self { query }
}
async fn view(this: Self) -> AnyView {
let Issue4296 { query } = this;
view! {
<a href="?q=abc">"abc"</a>
<a href="?q=def">"def"</a>
<p id="result">{move || format!("{:?}", query.get())}</p>
}
.into_any()
}
}

View File

@@ -1,9 +1,5 @@
pub mod app;
mod issue_4005;
mod issue_4088;
mod issue_4217;
mod issue_4285;
mod issue_4296;
mod pr_4015;
mod pr_4091;

View File

@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Actix integrations for the Leptos web framework."
version = "0.8.5"
version = { workspace = true }
rust-version.workspace = true
edition.workspace = true
@@ -22,10 +22,10 @@ leptos_meta = { workspace = true, features = ["nonce"] }
leptos_router = { workspace = true, features = ["ssr"] }
server_fn = { workspace = true, features = ["actix-no-default"] }
tachys = { workspace = true }
serde_json = { workspace = true, default-features = true }
serde_json = { workspace = true , default-features = true }
parking_lot = { workspace = true, default-features = true }
tracing = { optional = true, workspace = true, default-features = true }
tokio = { features = ["rt", "fs"], workspace = true, default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
tokio = { features = ["rt", "fs"] , workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }
dashmap = { workspace = true, default-features = true }

View File

@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Axum integrations for the Leptos web framework."
version = "0.8.6"
version = { workspace = true }
rust-version.workspace = true
edition.workspace = true

View File

@@ -1177,7 +1177,7 @@ where
generate_route_list_with_exclusions_and_ssg(app_fn, None).0
}
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use t.clone()his to automatically
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths.
#[cfg_attr(
@@ -2061,12 +2061,10 @@ where
req,
|app, chunks, _supports_ooo| {
Box::pin(async move {
let app = if cfg!(feature = "islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()
};
let app = app.collect::<String>().await;
let app = app
.to_html_stream_in_order()
.collect::<String>()
.await;
let chunks = chunks();
Box::pin(once(async move { app }).chain(chunks))
as PinnedStream<String>

View File

@@ -4,7 +4,7 @@ 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 = "0.8.5"
version = { workspace = true }
rust-version.workspace = true
edition.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos"
version = "0.8.9"
version = { workspace = true }
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -262,16 +262,6 @@ where
}
}
impl<C> From<View<C>> for ViewFn
where
C: Clone + Send + Sync + 'static,
View<C>: IntoAny,
{
fn from(value: View<C>) -> Self {
Self(Arc::new(move || value.clone().into_any()))
}
}
impl ViewFn {
/// Execute the wrapped function
pub fn run(&self) -> AnyView {
@@ -299,16 +289,6 @@ where
}
}
impl<C> From<View<C>> for ViewFnOnce
where
C: Send + Sync + 'static,
View<C>: IntoAny,
{
fn from(value: View<C>) -> Self {
Self(Box::new(move || value.into_any()))
}
}
impl ViewFnOnce {
/// Execute the wrapped function
pub fn run(self) -> AnyView {

View File

@@ -1,7 +1,3 @@
if (window.location.protocol === 'https:') {
protocol = 'wss://';
}
let host = window.location.hostname;
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
ws.onmessage = (ev) => {

View File

@@ -85,22 +85,12 @@
//! # Feature Flags
//!
//! - **`nightly`**: On `nightly` Rust, enables the function-call syntax for signal getters and setters.
//! Also enables some experimental optimizations that improve the handling of static strings and
//! the performance of the `template! {}` macro.
//! - **`csr`** Client-side rendering: Generate DOM nodes in the browser.
//! - **`ssr`** Server-side rendering: Generate an HTML string (typically on the server).
//! - **`islands`** Activates “islands mode,” in which components are not made interactive on the
//! client unless they use the `#[island]` macro.
//! - **`hydrate`** Hydration: use this to add interactivity to an SSRed Leptos app.
//! - **`nonce`** Adds support for nonces to be added as part of a Content Security Policy.
//! - **`rkyv`** In SSR/hydrate mode, enables using [`rkyv`](https://docs.rs/rkyv/latest/rkyv/) to serialize resources.
//! - **`rkyv`** In SSR/hydrate mode, uses [`rkyv`](https://docs.rs/rkyv/latest/rkyv/) to serialize resources and send them
//! from the server to the client.
//! - **`tracing`** Adds support for [`tracing`](https://docs.rs/tracing/latest/tracing/).
//! - **`trace-component-props`** Adds `tracing` support for component props.
//! - **`delegation`** Uses event delegation rather than the browsers native event handling
//! system. (This improves the performance of creating large numbers of elements simultaneously,
//! in exchange for occasional edge cases in which events behave differently from native browser
//! events.)
//! - **`rustls`** Use `rustls` for server functions.
//!
//! **Important Note:** You must enable one of `csr`, `hydrate`, or `ssr` to tell Leptos
//! which mode your app is operating in. You should only enable one of these per build target,
@@ -225,15 +215,12 @@ pub mod error {
/// Control-flow components like `<Show>`, `<For>`, and `<Await>`.
pub mod control_flow {
pub use crate::{
animated_show::*, await_::*, for_loop::*, show::*, show_let::*,
};
pub use crate::{animated_show::*, await_::*, for_loop::*, show::*};
}
mod animated_show;
mod await_;
mod for_loop;
mod show;
mod show_let;
/// A component that allows rendering a component somewhere else.
pub mod portal;
@@ -314,17 +301,12 @@ pub mod logging {
/// Utilities for working with asynchronous tasks.
pub mod task {
use any_spawner::Executor;
use reactive_graph::computed::ScopedFuture;
use std::future::Future;
/// Spawns a thread-safe [`Future`].
///
/// This will be run with the current reactive owner and observer using a [`ScopedFuture`].
#[track_caller]
#[inline(always)]
pub fn spawn(fut: impl Future<Output = ()> + Send + 'static) {
let fut = ScopedFuture::new(fut);
#[cfg(not(target_family = "wasm"))]
Executor::spawn(fut);

View File

@@ -1,162 +0,0 @@
use crate::{children::ViewFn, IntoView};
use leptos_macro::component;
use reactive_graph::traits::Get;
use std::{marker::PhantomData, sync::Arc};
use tachys::either::Either;
/// Like `<Show>` but for `Option`. This is a shortcut for
///
/// ```ignore
/// value.map(|value| {
/// view! { ... }
/// })
/// ```
///
/// If you specify a `fallback` it is equvalent to
///
/// ```ignore
/// value
/// .map(
/// |value| children(value),
/// )
/// .unwrap_or_else(fallback)
/// ```
///
/// ## Example
///
/// ```
/// # use leptos::prelude::*;
/// #
/// # #[component]
/// # pub fn Example() -> impl IntoView {
/// let (opt_value, set_opt_value) = signal(None::<i32>);
///
/// view! {
/// <ShowLet some=opt_value let:value>
/// "We have a value: " {value}
/// </ShowLet>
/// }
/// # }
/// ```
///
/// You can also specify a fallback:
/// ```
/// # use leptos::prelude::*;
/// #
/// # #[component]
/// # pub fn Example() -> impl IntoView {
/// let (opt_value, set_opt_value) = signal(None::<i32>);
///
/// view! {
/// <ShowLet some=opt_value let:value fallback=|| "Got nothing">
/// "We have a value: " {value}
/// </ShowLet>
/// }
/// # }
/// ```
///
/// In addition to signals you can also use a closure that returns an `Option`:
///
/// ```
/// # use leptos::prelude::*;
/// #
/// # #[component]
/// # pub fn Example() -> impl IntoView {
/// let (opt_value, set_opt_value) = signal(None::<i32>);
///
/// view! {
/// <ShowLet some=move || opt_value.get().map(|v| v * 2) let:value>
/// "We have a value: " {value}
/// </ShowLet>
/// }
/// # }
/// ```
#[component]
pub fn ShowLet<T, ChFn, V, M>(
/// The children will be shown whenever `value` is `Some`.
///
/// They take the inner value as an argument. Use `let:` to bind the value to a variable.
children: ChFn,
/// A signal of type `Option` or a closure that returns an `Option`.
/// If the value is `Some`, the children will be shown.
/// Otherwise the fallback will be shown, if present.
some: impl IntoOptionGetter<T, M>,
/// A closure that returns what gets rendered when the value is `None`.
/// By default this is the empty view.
///
/// You can think of it as the closure inside `.unwrap_or_else(|| fallback())`.
#[prop(optional, into)]
fallback: ViewFn,
/// Marker for generic parameters. Ignore this.
#[prop(optional)]
_marker: PhantomData<(T, M)>,
) -> impl IntoView
where
ChFn: Fn(T) -> V + Send + Clone + 'static,
V: IntoView + 'static,
T: 'static,
{
let getter = some.into_option_getter();
move || {
let children = children.clone();
let fallback = fallback.clone();
getter
.run()
.map(move |t| Either::Left(children(t)))
.unwrap_or_else(move || Either::Right(fallback.run()))
}
}
/// Servers as a wrapper for both, an `Option` signal or a closure that returns an `Option`.
pub struct OptionGetter<T>(Arc<dyn Fn() -> Option<T> + Send + Sync + 'static>);
impl<T> Clone for OptionGetter<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T> OptionGetter<T> {
/// Runs the getter and returns the result.
pub fn run(&self) -> Option<T> {
(self.0)()
}
}
/// Conversion trait for creating an `OptionGetter` from a closure or a signal.
pub trait IntoOptionGetter<T, M> {
/// Converts the given value into an `OptionGetter`.
fn into_option_getter(self) -> OptionGetter<T>;
}
/// Marker type for creating an `OptionGetter` from a closure.
/// Used so that the compiler doesn't complain about double implementations of the trait `IntoOptionGetter`.
pub struct FunctionMarker;
impl<T, F> IntoOptionGetter<T, FunctionMarker> for F
where
F: Fn() -> Option<T> + Send + Sync + 'static,
{
fn into_option_getter(self) -> OptionGetter<T> {
OptionGetter(Arc::new(self))
}
}
/// Marker type for creating an `OptionGetter` from a signal.
/// Used so that the compiler doesn't complain about double implementations of the trait `IntoOptionGetter`.
pub struct SignalMarker;
impl<T, S> IntoOptionGetter<T, SignalMarker> for S
where
S: Get<Value = Option<T>> + Clone + Send + Sync + 'static,
{
fn into_option_getter(self) -> OptionGetter<T> {
let cloned = self.clone();
OptionGetter(Arc::new(move || cloned.get()))
}
}

View File

@@ -32,12 +32,12 @@ use tachys::{
};
use throw_error::ErrorHookFuture;
/// If any [`Resource`](crate::prelude::Resource) is read in the `children` of this
/// 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::prelude::Transition).
/// 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

View File

@@ -16,11 +16,11 @@ use reactive_graph::{
use slotmap::{DefaultKey, SlotMap};
use tachys::reactive_graph::OwnedView;
/// If any [`Resource`](crate::prelude::Resource) is read in the `children` of this
/// 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::prelude::Suspense), this will not fall
/// 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

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Configuration for the Leptos web framework."
readme = "../README.md"
version = "0.8.7"
version = { workspace = true }
rust-version.workspace = true
edition.workspace = true
@@ -13,24 +13,16 @@ edition.workspace = true
config = { default-features = false, features = [
"toml",
"convert-case",
], workspace = true }
] , workspace = true }
regex = { workspace = true, default-features = true }
serde = { features = [
"derive",
"rc",
], workspace = true, default-features = true }
thiserror = { workspace = true, default-features = true }
typed-builder = { workspace = true, default-features = true }
serde = { features = ["derive", "rc"] , workspace = true, default-features = true }
thiserror = { workspace = true , default-features = true }
typed-builder = { workspace = true , default-features = true }
[dev-dependencies]
tokio = { features = [
"rt",
"macros",
], workspace = true, default-features = true }
tokio = { features = ["rt", "macros"] , workspace = true, default-features = true }
tempfile = { workspace = true, default-features = true }
temp-env = { features = [
"async_closure",
], workspace = true, default-features = true }
temp-env = { features = ["async_closure"] , workspace = true, default-features = true }
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_dom"
version = "0.8.6"
version = { workspace = true }
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -14,10 +14,10 @@ reactive_graph = { workspace = true }
or_poisoned = { workspace = true }
js-sys = { workspace = true, default-features = true }
send_wrapper = { workspace = true, default-features = true }
tracing = { optional = true, workspace = true, default-features = true }
wasm-bindgen = { workspace = true, default-features = true }
serde_json = { optional = true, workspace = true, default-features = true }
serde = { optional = true, workspace = true, default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
wasm-bindgen = { workspace = true , default-features = true }
serde_json = { optional = true , workspace = true, default-features = true }
serde = { optional = true , workspace = true, default-features = true }
[dev-dependencies]
leptos = { path = "../leptos" }

View File

@@ -258,7 +258,15 @@ pub fn request_idle_callback_with_handle(
///
/// <div class="warning">The task is called outside of the ownership tree, this means that if you want to access for example the context you need to reestablish the owner.</div>
pub fn queue_microtask(task: impl FnOnce() + 'static) {
tachys::renderer::dom::queue_microtask(task);
use js_sys::{Function, Reflect};
let task = Closure::once_into_js(task);
let window = web_sys::window().expect("window not available");
let queue_microtask =
Reflect::get(&window, &JsValue::from_str("queueMicrotask"))
.expect("queueMicrotask not available");
let queue_microtask = queue_microtask.unchecked_into::<Function>();
_ = queue_microtask.call1(&JsValue::UNDEFINED, &task);
}
/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.
@@ -585,8 +593,7 @@ impl WindowListenerHandle {
}
}
/// Returns `true` if the current environment is a server.
pub fn is_server() -> bool {
fn is_server() -> bool {
#[cfg(feature = "hydration")]
{
Owner::current_shared_context()
@@ -598,8 +605,3 @@ pub fn is_server() -> bool {
false
}
}
/// Returns `true` if the current environment is a browser.
pub fn is_browser() -> bool {
!is_server()
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_hot_reload"
version = "0.8.5"
version = { workspace = true }
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -11,20 +11,17 @@ edition.workspace = true
[dependencies]
anyhow = { workspace = true, default-features = true }
serde = { features = ["derive"], workspace = true, default-features = true }
serde = { features = ["derive"] , workspace = true, default-features = true }
syn = { features = [
"full",
"parsing",
"extra-traits",
"visit",
"printing",
], workspace = true, default-features = true }
] , workspace = true, default-features = true }
quote = { workspace = true, default-features = true }
rstml = { workspace = true, default-features = true }
proc-macro2 = { features = [
"span-locations",
"nightly",
], workspace = true, default-features = true }
proc-macro2 = { features = ["span-locations", "nightly"] , workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
walkdir = { workspace = true, default-features = true }
camino = { workspace = true, default-features = true }

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.8.8"
version = { workspace = true }
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -13,28 +13,26 @@ edition.workspace = true
proc-macro = true
[dependencies]
attribute-derive = { features = [
"syn-full",
], workspace = true, default-features = true }
attribute-derive = { features = ["syn-full"] , workspace = true, default-features = true }
cfg-if = { workspace = true, default-features = true }
html-escape = { workspace = true, default-features = true }
itertools = { workspace = true, default-features = true }
itertools = { workspace = true , default-features = true }
prettyplease = { workspace = true, default-features = true }
proc-macro-error2 = { default-features = false, workspace = true }
proc-macro-error2 = { default-features = false , workspace = true }
proc-macro2 = { workspace = true, default-features = true }
quote = { workspace = true, default-features = true }
syn = { features = ["full"], workspace = true, default-features = true }
syn = { features = ["full"] , workspace = true, default-features = true }
rstml = { workspace = true, default-features = true }
leptos_hot_reload = { workspace = true }
server_fn_macro = { workspace = true }
convert_case = { workspace = true, default-features = true }
uuid = { features = ["v4"], workspace = true, default-features = true }
tracing = { optional = true, workspace = true, default-features = true }
convert_case = { workspace = true , default-features = true }
uuid = { features = ["v4"] , workspace = true, default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
[dev-dependencies]
log = { workspace = true, default-features = true }
typed-builder = { workspace = true, default-features = true }
trybuild = { workspace = true, default-features = true }
trybuild = { workspace = true , default-features = true }
leptos = { path = "../leptos" }
leptos_router = { path = "../router", features = ["ssr"] }
server_fn = { path = "../server_fn", features = ["cbor"] }

View File

@@ -548,7 +548,7 @@ impl ToTokens for Model {
quote! {
#[::leptos::prelude::lazy]
#[allow(non_snake_case)]
fn #outer_name (el: ::leptos::web_sys::HtmlElement) {
async fn #outer_name (el: ::leptos::web_sys::HtmlElement) {
#hydrate_fn_inner
}
@@ -1360,10 +1360,7 @@ fn prop_to_doc(
}
pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
Ident::new(
&format!("__component_{}", ident.to_string().to_case(Snake)),
ident.span(),
)
Ident::new(&format!("__{ident}"), ident.span())
}
/// Converts all `impl Trait`s in a function signature to use generic params instead.

View File

@@ -683,11 +683,7 @@ fn component_macro(
let parse_result = syn::parse::<component::Model>(s);
if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
let expanded = model
.is_transparent(is_transparent)
.is_lazy(is_lazy)
.with_island(island)
.into_token_stream();
let expanded = model.is_transparent(is_transparent).is_lazy(is_lazy).with_island(island).into_token_stream();
if !matches!(unexpanded.vis, Visibility::Public(_)) {
unexpanded.vis = Visibility::Public(Pub {
span: unexpanded.vis.span(),
@@ -700,7 +696,7 @@ fn component_macro(
#expanded
#[doc(hidden)]
#[allow(clippy::too_many_arguments, clippy::needless_lifetimes)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
#unexpanded
}
} else {
@@ -709,7 +705,7 @@ fn component_macro(
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
quote! {
#[doc(hidden)]
#[allow(clippy::too_many_arguments, clippy::needless_lifetimes)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
#dummy
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_server"
version = "0.8.5"
version = { workspace = true }
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -11,11 +11,11 @@ edition.workspace = true
[dependencies]
base64 = { workspace = true, default-features = true }
codee = { features = ["json_serde"], workspace = true, default-features = true }
codee = { features = ["json_serde"] , workspace = true, default-features = true }
hydration_context = { workspace = true }
reactive_graph = { workspace = true, features = ["hydration"] }
server_fn = { workspace = true }
tracing = { optional = true, workspace = true, default-features = true }
tracing = { optional = true , workspace = true, default-features = true }
futures = { workspace = true, default-features = true }
any_spawner = { workspace = true }
@@ -25,9 +25,9 @@ send_wrapper = { workspace = true, default-features = true }
# serialization formats
serde = { workspace = true, default-features = true }
js-sys = { optional = true, workspace = true, default-features = true }
wasm-bindgen = { workspace = true, optional = true, default-features = true }
serde_json = { workspace = true, default-features = true }
js-sys = { optional = true , workspace = true, default-features = true }
wasm-bindgen = { workspace = true, optional = true , default-features = true }
serde_json = { workspace = true , default-features = true }
[features]
ssr = []
@@ -44,8 +44,7 @@ denylist = ["tracing"]
max_combination_size = 2
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition", "--cfg", "docsrs"]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -386,7 +386,6 @@ T: Send + Sync + 'static,
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> ArcOnceResource<T, JsonSerdeWasmCodec>
where
T: Send + Sync + 'static,
@@ -419,7 +418,6 @@ fut: impl Future<Output = T> + Send + 'static
}
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> ArcOnceResource<T, MiniserdeCodec>
where
T: Send + Sync + 'static,
@@ -453,7 +451,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> ArcOnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
@@ -487,7 +484,6 @@ fut: impl Future<Output = T> + Send + 'static
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> ArcOnceResource<T, RkyvCodec>
where
T: Send + Sync + 'static,
@@ -752,7 +748,6 @@ T: Send + Sync + 'static,
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> OnceResource<T, JsonSerdeWasmCodec>
where
T: Send + Sync + 'static,
@@ -785,7 +780,6 @@ fut: impl Future<Output = T> + Send + 'static
}
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> OnceResource<T, MiniserdeCodec>
where
T: Send + Sync + 'static,
@@ -819,7 +813,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> OnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
@@ -853,7 +846,6 @@ fut: impl Future<Output = T> + Send + 'static
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> OnceResource<T, RkyvCodec>
where
T: Send + Sync + 'static,

View File

@@ -709,7 +709,6 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> ArcResource<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,
@@ -1049,7 +1048,6 @@ where
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> Resource<T, JsonSerdeWasmCodec>
where
JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
@@ -1107,7 +1105,6 @@ where
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> Resource<T, MiniserdeCodec>
where
MiniserdeCodec: Encoder<T> + Decoder<T>,
@@ -1167,7 +1164,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> Resource<T, SerdeLite<JsonSerdeCodec>>
where
SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
@@ -1226,7 +1222,6 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> Resource<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,

View File

@@ -80,7 +80,6 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> SharedValue<T, SerdeLite<JsonSerdeCodec>>
where
SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
@@ -103,7 +102,6 @@ where
}
#[cfg(feature = "serde-wasm-bindgen")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-wasm-bindgen")))]
impl<T> SharedValue<T, JsonSerdeWasmCodec>
where
JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
@@ -126,7 +124,6 @@ where
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> SharedValue<T, MiniserdeCodec>
where
MiniserdeCodec: Encoder<T> + Decoder<T>,
@@ -149,7 +146,6 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> SharedValue<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.8.5"
version = { workspace = true }
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.2.7"
version = "0.2.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
@@ -27,7 +27,6 @@ async-lock = { workspace = true, default-features = true }
send_wrapper = { features = [
"futures",
], workspace = true, default-features = true }
indexmap = { workspace = true, default-features = true }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
web-sys = { version = "0.3.77", features = ["console"] }

View File

@@ -1,8 +1,7 @@
use crate::{
computed::{ArcMemo, Memo, ScopedFuture},
computed::{ArcMemo, Memo},
diagnostics::is_suppressing_resource_load,
graph::untrack,
owner::{ArcStoredValue, ArenaItem, Owner},
owner::{ArcStoredValue, ArenaItem},
send_wrapper_ext::SendOption,
signal::{ArcMappedSignal, ArcRwSignal, MappedSignal, RwSignal},
traits::{DefinedAt, Dispose, Get, GetUntracked, GetValue, Update, Write},
@@ -200,18 +199,13 @@ where
I: Send + Sync,
O: Send + Sync,
{
let owner = Owner::current().unwrap_or_default();
ArcAction {
in_flight: ArcRwSignal::new(0),
input: ArcRwSignal::new(SendOption::new(None)),
value: ArcRwSignal::new(SendOption::new(value)),
version: Default::default(),
dispatched: Default::default(),
action_fn: Arc::new(move |input| {
Box::pin(owner.with(|| {
ScopedFuture::new_untracked(untrack(|| action_fn(input)))
}))
}),
action_fn: Arc::new(move |input| Box::pin(action_fn(input))),
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),
}
@@ -376,7 +370,6 @@ where
F: Fn(&I) -> Fu + 'static,
Fu: Future<Output = O> + 'static,
{
let owner = Owner::current().unwrap_or_default();
let action_fn = SendWrapper::new(action_fn);
ArcAction {
in_flight: ArcRwSignal::new(0),
@@ -385,9 +378,7 @@ where
version: Default::default(),
dispatched: Default::default(),
action_fn: Arc::new(move |input| {
Box::pin(SendWrapper::new(owner.with(|| {
ScopedFuture::new_untracked(untrack(|| action_fn(input)))
})))
Box::pin(SendWrapper::new(action_fn(input)))
}),
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: Location::caller(),

View File

@@ -521,10 +521,9 @@ impl<T: 'static> ArcAsyncDerived<T> {
{
let fun = move || {
let fut = fun();
let fut =
ScopedFuture::new_untracked_with_diagnostics(async move {
SendOption::new(Some(fut.await))
});
let fut = ScopedFuture::new_untracked(async move {
SendOption::new(Some(fut.await))
});
#[cfg(feature = "sandboxed-arenas")]
let fut = Sandboxed::new(fut);
fut

View File

@@ -54,55 +54,11 @@ impl<Fut> ScopedFuture<Fut> {
fut,
}
}
#[doc(hidden)]
#[track_caller]
pub fn new_untracked_with_diagnostics(
fut: Fut,
) -> ScopedFutureUntrackedWithDiagnostics<Fut> {
let owner = Owner::current().unwrap_or_default();
ScopedFutureUntrackedWithDiagnostics {
owner,
observer: None,
fut,
}
}
}
impl<Fut: Future> Future for ScopedFuture<Fut> {
type Output = Fut::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
this.owner.with(|| {
#[cfg(debug_assertions)]
let _maybe_guard = if this.observer.is_none() {
Some(crate::diagnostics::SpecialNonReactiveZone::enter())
} else {
None
};
this.observer.with_observer(|| this.fut.poll(cx))
})
}
}
pin_project! {
/// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner
/// `Future`, output of [`ScopedFuture::new_untracked_with_diagnostics`].
///
/// In leptos 0.9 this will be replaced with `ScopedFuture` itself.
#[derive(Clone)]
pub struct ScopedFutureUntrackedWithDiagnostics<Fut> {
owner: Owner,
observer: Option<AnySubscriber>,
#[pin]
fut: Fut,
}
}
impl<Fut: Future> Future for ScopedFutureUntrackedWithDiagnostics<Fut> {
type Output = Fut::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
this.owner

View File

@@ -6,14 +6,11 @@
//! a linear search is not significantly more expensive than a hash and lookup.
use super::{AnySource, AnySubscriber, Source};
use indexmap::IndexSet;
use rustc_hash::FxHasher;
use std::{hash::BuildHasherDefault, mem};
type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
use core::slice;
use std::{mem, vec::IntoIter};
#[derive(Default, Clone, Debug)]
pub struct SourceSet(FxIndexSet<AnySource>);
pub struct SourceSet(Vec<AnySource>);
impl SourceSet {
pub fn new() -> Self {
@@ -21,14 +18,16 @@ impl SourceSet {
}
pub fn insert(&mut self, source: AnySource) {
self.0.insert(source);
self.0.push(source);
}
pub fn remove(&mut self, source: &AnySource) {
self.0.shift_remove(source);
if let Some(pos) = self.0.iter().position(|s| s == source) {
self.0.remove(pos);
}
}
pub fn take(&mut self) -> FxIndexSet<AnySource> {
pub fn take(&mut self) -> Vec<AnySource> {
mem::take(&mut self.0)
}
@@ -45,7 +44,7 @@ impl SourceSet {
impl IntoIterator for SourceSet {
type Item = AnySource;
type IntoIter = <FxIndexSet<AnySource> as IntoIterator>::IntoIter;
type IntoIter = IntoIter<AnySource>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
@@ -54,36 +53,40 @@ impl IntoIterator for SourceSet {
impl<'a> IntoIterator for &'a SourceSet {
type Item = &'a AnySource;
type IntoIter = <&'a FxIndexSet<AnySource> as IntoIterator>::IntoIter;
type IntoIter = slice::Iter<'a, AnySource>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[derive(Debug, Default, Clone)]
pub struct SubscriberSet(FxIndexSet<AnySubscriber>);
pub struct SubscriberSet(Vec<AnySubscriber>);
impl SubscriberSet {
pub fn new() -> Self {
Self(FxIndexSet::with_capacity_and_hasher(2, Default::default()))
Self(Vec::with_capacity(2))
}
pub fn subscribe(&mut self, subscriber: AnySubscriber) {
self.0.insert(subscriber);
if !self.0.contains(&subscriber) {
self.0.push(subscriber);
}
}
pub fn unsubscribe(&mut self, subscriber: &AnySubscriber) {
// note: do not use `.swap_remove()` here.
// using `.remove()` is slower because it shifts other items
// but it maintains the order of the subscribers, which is important
// to correctness when you're using this to drive something like a UI,
// which can have nested effects, where the inner one assumes the outer
// has already run (for example, an outer effect that checks .is_some(),
// and an inner effect that unwraps)
self.0.shift_remove(subscriber);
if let Some(pos) = self.0.iter().position(|s| s == subscriber) {
// note: do not use `.swap_remove()` here.
// using `.remove()` is slower because it shifts other items
// but it maintains the order of the subscribers, which is important
// to correctness when you're using this to drive something like a UI,
// which can have nested effects, where the inner one assumes the outer
// has already run (for example, an outer effect that checks .is_some(),
// and an inner effect that unwraps)
self.0.remove(pos);
}
}
pub fn take(&mut self) -> FxIndexSet<AnySubscriber> {
pub fn take(&mut self) -> Vec<AnySubscriber> {
mem::take(&mut self.0)
}
@@ -94,7 +97,7 @@ impl SubscriberSet {
impl IntoIterator for SubscriberSet {
type Item = AnySubscriber;
type IntoIter = <FxIndexSet<AnySubscriber> as IntoIterator>::IntoIter;
type IntoIter = IntoIter<AnySubscriber>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
@@ -103,7 +106,7 @@ impl IntoIterator for SubscriberSet {
impl<'a> IntoIterator for &'a SubscriberSet {
type Item = &'a AnySubscriber;
type IntoIter = <&'a FxIndexSet<AnySubscriber> as IntoIterator>::IntoIter;
type IntoIter = slice::Iter<'a, AnySubscriber>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()

View File

@@ -257,20 +257,6 @@ pub mod read {
}
}
impl<T, S> From<ReadSignal<T, S>> for ArcSignal<T, S>
where
S: Storage<ArcReadSignal<T>> + Storage<T>,
{
#[track_caller]
fn from(value: ReadSignal<T, S>) -> Self {
Self {
inner: SignalTypes::ReadSignal(value.into()),
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: std::panic::Location::caller(),
}
}
}
impl<T: Send + Sync> From<ArcRwSignal<T>> for ArcSignal<T, SyncStorage> {
#[track_caller]
fn from(value: ArcRwSignal<T>) -> Self {
@@ -282,20 +268,6 @@ pub mod read {
}
}
impl<T, S> From<RwSignal<T, S>> for ArcSignal<T, S>
where
S: Storage<ArcRwSignal<T>> + Storage<ArcReadSignal<T>> + Storage<T>,
{
#[track_caller]
fn from(value: RwSignal<T, S>) -> Self {
Self {
inner: SignalTypes::ReadSignal(value.read_only().into()),
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: std::panic::Location::caller(),
}
}
}
impl<T, S> From<ArcMemo<T, S>> for ArcSignal<T, S>
where
S: Storage<T>,
@@ -310,20 +282,6 @@ pub mod read {
}
}
impl<T, S> From<Memo<T, S>> for ArcSignal<T, S>
where
S: Storage<ArcMemo<T, S>> + Storage<T>,
{
#[track_caller]
fn from(value: Memo<T, S>) -> Self {
Self {
inner: SignalTypes::Memo(value.into()),
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: std::panic::Location::caller(),
}
}
}
impl<T, S> DefinedAt for ArcSignal<T, S>
where
S: Storage<T>,

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.2.6"
version = "0.2.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -6,8 +6,8 @@ use syn::{
parse::{Parse, ParseStream, Parser},
punctuated::Punctuated,
token::Comma,
ExprClosure, Field, Fields, GenericParam, Generics, Ident, Index, Meta,
Result, Token, Type, TypeParam, Variant, Visibility, WhereClause,
ExprClosure, Field, Fields, Generics, Ident, Index, Meta, Result, Token,
Type, Variant, Visibility, WhereClause,
};
#[proc_macro_error]
@@ -26,103 +26,6 @@ pub fn derive_patch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
.into()
}
/// Removes all constraints from generics arguments list.
///
/// # Example
///
/// ```rust,ignore
/// struct Data<
/// 'a,
/// T1: ToString + PatchField,
/// T2: PatchField,
/// T3: 'static + PatchField,
/// T4,
/// >
/// where
/// T3: ToString,
/// T4: ToString + PatchField,
/// {
/// data1: &'a T1,
/// data2: T2,
/// data3: T3,
/// data4: T4,
/// }
/// ```
///
/// Fort the struct above the `[syn::DeriveInput::parse]` will return the instance of [syn::Generics]
/// which will conceptually look like this
///
/// ```text
/// Generics:
/// params:
/// [
/// 'a,
/// T1: ToString + PatchField,
/// T2: PatchField,
/// T3: 'static + PatchField,
/// T4,
/// ]
/// where_clause:
/// [
/// T3: ToString,
/// T4: ToString + PatchField,
/// ]
/// ```
///
/// This method would return a new instance of [syn::Generics] which will conceptually look like this
///
/// ```text
/// Generics:
/// params:
/// [
/// 'a,
/// T1,
/// T2,
/// T3,
/// T4,
/// ]
/// where_clause:
/// []
/// ```
///
/// This is useful when you want to use a generic arguments list for `impl` sections for type definitions.
fn remove_constraint_from_generics(generics: &Generics) -> Generics {
let mut new_generics = generics.clone();
// remove contraints directly placed in the generic arguments list
//
// For generics for `struct A<T: MyTrait>` the `T: MyTrait` becomes `T`
for param in new_generics.params.iter_mut() {
match param {
GenericParam::Lifetime(lifetime) => {
lifetime.bounds.clear(); // remove bounds
lifetime.colon_token = None;
}
GenericParam::Type(type_param) => {
type_param.bounds.clear(); // remove bounds
type_param.colon_token = None;
type_param.eq_token = None;
type_param.default = None;
}
GenericParam::Const(const_param) => {
// replaces const generic with type param without bounds which is basically an `ident` token
*param = GenericParam::Type(TypeParam {
attrs: const_param.attrs.clone(),
ident: const_param.ident.clone(),
colon_token: None,
bounds: Punctuated::new(),
eq_token: None,
default: None,
});
}
}
}
new_generics.where_clause = None; // remove where clause
new_generics
}
struct Model {
vis: Visibility,
name: Ident,
@@ -208,9 +111,7 @@ impl ToTokens for Model {
} = &self;
let any_store_field = Ident::new("AnyStoreField", Span::call_site());
let trait_name = Ident::new(&format!("{name}StoreFields"), name.span());
let clear_generics = remove_constraint_from_generics(generics);
let params = &generics.params;
let clear_params = &clear_generics.params;
let generics_with_orig = quote! { <#any_store_field, #params> };
let where_with_orig = {
generics
@@ -223,22 +124,17 @@ impl ToTokens for Model {
} = &w;
quote! {
#where_token
#any_store_field: #library_path::StoreField<Value = #name < #clear_params > >,
#any_store_field: #library_path::StoreField<Value = #name #generics>,
#predicates
}
})
.unwrap_or_else(|| quote! { where #any_store_field: #library_path::StoreField<Value = #name < #clear_params > > })
.unwrap_or_else(|| quote! { where #any_store_field: #library_path::StoreField<Value = #name #generics> })
};
// define an extension trait that matches this struct
// and implement that trait for all StoreFields
let (trait_fields, read_fields): (Vec<_>, Vec<_>) = ty.to_field_data(
&library_path,
generics,
&clear_generics,
&any_store_field,
name,
);
let (trait_fields, read_fields): (Vec<_>, Vec<_>) =
ty.to_field_data(&library_path, generics, &any_store_field, name);
// read access
tokens.extend(quote! {
@@ -248,7 +144,7 @@ impl ToTokens for Model {
#(#trait_fields)*
}
impl #generics_with_orig #trait_name <AnyStoreField, #clear_params> for AnyStoreField
impl #generics_with_orig #trait_name <AnyStoreField, #params> for AnyStoreField
#where_with_orig
{
#(#read_fields)*
@@ -262,7 +158,6 @@ impl ModelTy {
&self,
library_path: &TokenStream,
generics: &Generics,
clear_generics: &Generics,
any_store_field: &Ident,
name: &Ident,
) -> (Vec<TokenStream>, Vec<TokenStream>) {
@@ -309,7 +204,6 @@ impl ModelTy {
library_path,
ident.as_ref(),
generics,
clear_generics,
any_store_field,
name,
ty,
@@ -321,7 +215,6 @@ impl ModelTy {
library_path,
ident.as_ref(),
generics,
clear_generics,
any_store_field,
name,
ty,
@@ -340,7 +233,6 @@ impl ModelTy {
library_path,
ident,
generics,
clear_generics,
any_store_field,
name,
fields,
@@ -350,7 +242,6 @@ impl ModelTy {
library_path,
ident,
generics,
clear_generics,
any_store_field,
name,
fields,
@@ -369,8 +260,7 @@ fn field_to_tokens(
modes: Option<&[SubfieldMode]>,
library_path: &proc_macro2::TokenStream,
orig_ident: Option<&Ident>,
_generics: &Generics,
clear_generics: &Generics,
generics: &Generics,
any_store_field: &Ident,
name: &Ident,
ty: &Type,
@@ -395,7 +285,7 @@ fn field_to_tokens(
SubfieldMode::Keyed(keyed_by, key_ty) => {
let signature = quote! {
#[track_caller]
fn #ident(self) -> #library_path::KeyedSubfield<#any_store_field, #name #clear_generics, #key_ty, #ty>
fn #ident(self) -> #library_path::KeyedSubfield<#any_store_field, #name #generics, #key_ty, #ty>
};
return if include_body {
quote! {
@@ -428,7 +318,7 @@ fn field_to_tokens(
// default subfield
if include_body {
quote! {
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #clear_generics, #ty> {
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #generics, #ty> {
#library_path::Subfield::new(
self,
#idx.into(),
@@ -439,7 +329,7 @@ fn field_to_tokens(
}
} else {
quote! {
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #clear_generics, #ty>;
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #generics, #ty>;
}
}
}
@@ -449,8 +339,7 @@ fn variant_to_tokens(
include_body: bool,
library_path: &proc_macro2::TokenStream,
ident: &Ident,
_generics: &Generics,
clear_generics: &Generics,
generics: &Generics,
any_store_field: &Ident,
name: &Ident,
fields: &Fields,
@@ -519,7 +408,7 @@ fn variant_to_tokens(
// default subfield
if include_body {
quote! {
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>> {
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>> {
#library_path::StoreField::track_field(&self);
let reader = #library_path::StoreField::reader(&self);
let matches = reader
@@ -551,7 +440,7 @@ fn variant_to_tokens(
}
} else {
quote! {
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>>;
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>>;
}
}
}));
@@ -602,7 +491,7 @@ fn variant_to_tokens(
// default subfield
if include_body {
quote! {
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>> {
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>> {
#library_path::StoreField::track_field(&self);
let reader = #library_path::StoreField::reader(&self);
let matches = reader
@@ -634,7 +523,7 @@ fn variant_to_tokens(
}
} else {
quote! {
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>>;
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>>;
}
}
}));
@@ -776,14 +665,9 @@ impl ToTokens for PatchModel {
}
};
let clear_generics = remove_constraint_from_generics(generics);
let params = clear_generics.params;
let where_clause = &generics.where_clause;
// read access
tokens.extend(quote! {
impl #generics #library_path::PatchField for #name <#params>
#where_clause
impl #library_path::PatchField for #name #generics
{
fn patch_field(
&mut self,

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.8.7"
version = { workspace = true }
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -221,13 +221,6 @@ impl LocationProvider for BrowserUrl {
fn complete_navigation(&self, loc: &LocationChange) {
let history = window().history().unwrap();
let current_path = self
.path_stack
.read_value()
.last()
.map(|url| url.to_full_path());
let add_to_stack = current_path.as_ref() != Some(&loc.value);
if loc.replace {
history
.replace_state_with_url(
@@ -236,7 +229,7 @@ impl LocationProvider for BrowserUrl {
Some(&loc.value),
)
.unwrap();
} else if add_to_stack {
} else {
// push the "forward direction" marker
let state = &loc.state.to_js_value();
history
@@ -247,9 +240,7 @@ impl LocationProvider for BrowserUrl {
// add this URL to the "path stack" for detecting back navigations, and
// unset "navigating back" state
if let Ok(url) = Self::current() {
if add_to_stack {
self.path_stack.write_value().push(url);
}
self.path_stack.write_value().push(url);
self.is_back.set(false);
}

View File

@@ -10,23 +10,14 @@ use crate::{
};
use any_spawner::Executor;
use either_of::{Either, EitherOf3};
use futures::{
channel::oneshot,
future::{join_all, AbortHandle, Abortable},
FutureExt,
};
use leptos::{
attr::any_attribute::AnyAttribute,
component,
oco::Oco,
prelude::{ArcStoredValue, WriteValue},
};
use futures::{channel::oneshot, future::join_all, FutureExt};
use leptos::{attr::any_attribute::AnyAttribute, component, oco::Oco};
use or_poisoned::OrPoisoned;
use reactive_graph::{
computed::{ArcMemo, ScopedFuture},
owner::{provide_context, use_context, Owner},
signal::{ArcRwSignal, ArcTrigger},
traits::{Get, GetUntracked, Notify, ReadUntracked, Set, Track, Write},
traits::{Get, GetUntracked, Notify, ReadUntracked, Set, Track},
transition::AsyncTransition,
wrappers::write::SignalSetter,
};
@@ -77,7 +68,6 @@ where
// held to keep the Owner alive until the router is dropped
#[allow(unused)]
outer_owner: Owner,
abort_navigation: ArcStoredValue<Option<AbortHandle>>,
}
impl<Loc, Defs, FalFn, Fal> Render for NestedRoutesView<Loc, Defs, FalFn>
@@ -119,7 +109,6 @@ where
base,
&mut loaders,
&mut outlets,
&outer_owner,
);
drop(url);
@@ -145,7 +134,6 @@ where
outlets,
view,
outer_owner,
abort_navigation: Default::default(),
}
}
@@ -160,14 +148,13 @@ where
}
return;
}
// since the path didn't match, we'll update the retained path for future diffing
state.path.clear();
state.path.push_str(url_snapshot.path());
let new_match = self.routes.match_route(url_snapshot.path());
*state.current_url.write_untracked() = url_snapshot;
state.current_url.set(url_snapshot);
match new_match {
None => {
@@ -194,51 +181,30 @@ where
&mut state.outlets,
self.set_is_routing.is_some(),
0,
&self.outer_owner,
);
let (abort_handle, abort_registration) =
AbortHandle::new_pair();
if let Some(prev_handle) =
state.abort_navigation.write_value().replace(abort_handle)
{
prev_handle.abort();
}
let location = self.location.clone();
let is_back = location
.as_ref()
.map(|nav| nav.is_back().get_untracked())
.unwrap_or(false);
Executor::spawn_local(async move {
let triggers = Abortable::new(
join_all(preloaders),
abort_registration,
);
if let Ok(triggers) = triggers.await {
// tell each one of the outlet triggers that it's ready
let notify = move || {
for trigger in triggers {
trigger.notify();
}
};
if self.transition {
start_view_transition(
different_level,
is_back,
notify,
);
} else {
notify();
let triggers = join_all(preloaders).await;
// tell each one of the outlet triggers that it's ready
let notify = move || {
for trigger in triggers {
trigger.notify();
}
};
if self.transition {
start_view_transition(different_level, is_back, notify);
} else {
notify();
}
});
let abort_navigation = state.abort_navigation.clone();
Executor::spawn_local(async move {
join_all(full_loaders).await;
_ = abort_navigation.write_value().take();
if let Some(set_is_routing) = self.set_is_routing {
set_is_routing.set(false);
}
@@ -372,7 +338,6 @@ where
base,
&mut loaders,
&mut outlets,
&outer_owner,
);
// outlets will not send their views if the loaders are never polled
@@ -426,16 +391,8 @@ where
base,
&mut loaders,
&mut outlets,
&outer_owner,
);
let preload_owners = outlets
.iter()
.map(|o| o.preload_owner.clone())
.collect::<Vec<_>>();
outer_owner
.with(|| Owner::on_cleanup(move || drop(preload_owners)));
// outlets will not send their views if the loaders are never polled
// the loaders are async so that they can lazy-load routes in the browser,
// but they should always be synchronously available on the server
@@ -487,7 +444,6 @@ where
base,
&mut loaders,
&mut outlets,
&outer_owner,
);
drop(url);
@@ -507,7 +463,6 @@ where
outlets,
view,
outer_owner,
abort_navigation: Default::default(),
}
}
@@ -543,7 +498,6 @@ where
base,
&mut loaders,
&mut outlets,
&outer_owner,
);
drop(url);
@@ -560,7 +514,6 @@ where
outlets,
view,
outer_owner,
abort_navigation: Default::default(),
}
}
@@ -580,7 +533,6 @@ pub(crate) struct RouteContext {
base: Option<Oco<'static, str>>,
view_fn: Arc<Mutex<OutletViewFn>>,
owner: Arc<Mutex<Option<Owner>>>,
preload_owner: Owner,
child: ChildRoute,
}
@@ -612,7 +564,6 @@ impl Clone for RouteContext {
view_fn: Arc::clone(&self.view_fn),
owner: Arc::clone(&self.owner),
child: self.child.clone(),
preload_owner: self.preload_owner.clone(),
}
}
}
@@ -624,7 +575,6 @@ trait AddNestedRoute {
base: Option<Oco<'static, str>>,
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
outlets: &mut Vec<RouteContext>,
outer_owner: &Owner,
);
#[allow(clippy::too_many_arguments)]
@@ -638,7 +588,6 @@ trait AddNestedRoute {
outlets: &mut Vec<RouteContext>,
set_is_routing: bool,
level: u8,
outer_owner: &Owner,
) -> u8;
}
@@ -652,7 +601,6 @@ where
base: Option<Oco<'static, str>>,
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
outlets: &mut Vec<RouteContext>,
outer_owner: &Owner,
) {
let orig_url = url;
@@ -720,7 +668,6 @@ where
base: base.clone(),
child: ChildRoute(Arc::new(Mutex::new(None))),
owner: Arc::new(Mutex::new(None)),
preload_owner: outer_owner.child(),
};
if !outlets.is_empty() {
let prev_index = outlets.len().saturating_sub(1);
@@ -745,15 +692,7 @@ where
provide_context(params.clone());
provide_context(url.clone());
provide_context(matched.clone());
outlet
.preload_owner
.with(|| {
provide_context(params.clone());
provide_context(url.clone());
provide_context(matched.clone());
ScopedFuture::new(view.preload())
})
.await;
view.preload().await;
let child = outlet.child.clone();
*view_fn.lock().or_poisoned() =
Box::new(move |owner_where_used| {
@@ -800,13 +739,7 @@ where
// this is important because to build the view, we need access to the outlet
// and the outlet will be returned from building this child
if let Some(child) = child {
child.build_nested_route(
orig_url,
base,
loaders,
outlets,
outer_owner,
);
child.build_nested_route(orig_url, base, loaders, outlets);
}
}
@@ -821,7 +754,6 @@ where
outlets: &mut Vec<RouteContext>,
set_is_routing: bool,
level: u8,
outer_owner: &Owner,
) -> u8 {
let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
.iter()
@@ -838,13 +770,7 @@ where
match current {
// if there's nothing currently in the routes at this point, build from here
None => {
self.build_nested_route(
url,
base,
preloaders,
outlets,
outer_owner,
);
self.build_nested_route(url, base, preloaders, outlets);
level
}
Some(current) => {
@@ -884,10 +810,6 @@ where
&mut current.matched,
ArcRwSignal::new(new_match),
);
let old_preload_owner = mem::replace(
&mut current.preload_owner,
outer_owner.child(),
);
let matched_including_parents = {
ArcMemo::new({
let matched = current.matched.clone();
@@ -930,26 +852,11 @@ where
let child = outlet.child.clone();
async move {
let child = child.clone();
outlet
.preload_owner
.with(|| {
provide_context(
params_including_parents.clone(),
);
provide_context(url.clone());
provide_context(matched.clone());
ScopedFuture::new(async {
if set_is_routing {
AsyncTransition::run(|| {
view.preload()
})
.await;
} else {
view.preload().await;
}
})
})
.await;
if set_is_routing {
AsyncTransition::run(|| view.preload()).await;
} else {
view.preload().await;
}
*view_fn.lock().or_poisoned() =
Box::new(move |owner_where_used| {
let prev_owner = route_owner
@@ -998,7 +905,6 @@ where
drop(old_params);
drop(old_url);
drop(old_matched);
drop(old_preload_owner);
trigger
}
})));
@@ -1009,13 +915,8 @@ where
// if this children has matches, then rebuild the lower section of the tree
if let Some(child) = child {
child.build_nested_route(
url,
base,
preloaders,
outlets,
outer_owner,
);
child
.build_nested_route(url, base, preloaders, outlets);
} else {
*outlets[*items].child.0.lock().or_poisoned() = None;
}
@@ -1039,7 +940,6 @@ where
outlets,
set_is_routing,
level + 1,
outer_owner,
)
} else {
*current.child.0.lock().or_poisoned() = None;

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.8.5"
version = { workspace = true }
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env bash
set -e
LAST_TAG=$(git describe --tags --abbrev=0 --match "v*")
# Get package name and manifest_path for all members
PACKAGES=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[] | "\(.name):::\(.manifest_path)"')
for PKG in $PACKAGES; do
NAME="${PKG%%:::*}"
MANIFEST_PATH="${PKG##*:::}"
DIR=$(dirname "$MANIFEST_PATH")
# Look for release commit for this member up to the last tag
RELEASE_COMMIT=$(git log --oneline --grep="^$NAME-v" --format="%H" "$LAST_TAG"..HEAD | head -n1)
if [[ -z "$RELEASE_COMMIT" ]]; then
# No release commit found, use the latest release tag commit
RELEASE_COMMIT=$(git rev-list -n 1 "$LAST_TAG")
fi
# Check if any file in the package directory changed since the member's release commit or latest tag release
if git diff --quiet "$RELEASE_COMMIT"..HEAD -- "$DIR"; then
continue
fi
echo "Changes detected in $NAME ($DIR)"
PS3="Select version bump for $NAME: "
select BUMP in patch minor major; do
if [[ "$BUMP" == "patch" || "$BUMP" == "minor" || "$BUMP" == "major" ]]; then
break
else
echo "Invalid option"
fi
done
if cargo set-version --help >/dev/null 2>&1; then
cargo set-version --bump "$BUMP" --package "$NAME"
else
echo "Please install cargo-edit first."
exit 1
fi
echo "$NAME bumped to $BUMP"
done

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "RPC for any web framework."
readme = "../README.md"
version = "0.8.7"
version = { workspace = true }
rust-version.workspace = true
edition.workspace = true

View File

@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "The default implementation of the server_fn macro without a context"
version = "0.8.5"
version = { workspace = true }
edition.workspace = true
[lib]

View File

@@ -30,8 +30,8 @@ where
})?;
Request::try_new_patch_bytes(
path,
Encoding::CONTENT_TYPE,
accepts,
Encoding::CONTENT_TYPE,
data,
)
}

View File

@@ -28,7 +28,7 @@ where
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post_bytes(path, Encoding::CONTENT_TYPE, accepts, data)
Request::try_new_post_bytes(path, accepts, Encoding::CONTENT_TYPE, data)
}
}

View File

@@ -28,7 +28,7 @@ where
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_put_bytes(path, Encoding::CONTENT_TYPE, accepts, data)
Request::try_new_put_bytes(path, accepts, Encoding::CONTENT_TYPE, data)
}
}

View File

@@ -13,7 +13,7 @@ pub struct GetUrl;
/// Pass arguments as the URL-encoded body of a `POST` request.
pub struct PostUrl;
/// Pass arguments as the URL-encoded query string of a `DELETE` request.
/// Pass arguments as the URL-encoded body of a `DELETE` request.
/// **Note**: Browser support for `DELETE` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub struct DeleteUrl;
@@ -46,7 +46,7 @@ where
let data = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_get(path, GetUrl::CONTENT_TYPE, accepts, &data)
Request::try_new_get(path, accepts, GetUrl::CONTENT_TYPE, &data)
}
}
@@ -85,7 +85,7 @@ where
let qs = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post(path, PostUrl::CONTENT_TYPE, accepts, qs)
Request::try_new_post(path, accepts, PostUrl::CONTENT_TYPE, qs)
}
}
@@ -124,7 +124,7 @@ where
let data = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_delete(path, DeleteUrl::CONTENT_TYPE, accepts, &data)
Request::try_new_delete(path, accepts, GetUrl::CONTENT_TYPE, &data)
}
}
@@ -163,7 +163,7 @@ where
let data = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_patch(path, PatchUrl::CONTENT_TYPE, accepts, data)
Request::try_new_patch(path, accepts, GetUrl::CONTENT_TYPE, data)
}
}
@@ -174,9 +174,9 @@ where
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, E> {
let string_data = req.try_into_string().await?;
let string_data = req.as_query().unwrap_or_default();
let args = serde_qs::Config::new(5, false)
.deserialize_str::<Self>(&string_data)
.deserialize_str::<Self>(string_data)
.map_err(|e| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;
@@ -202,7 +202,7 @@ where
let data = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_put(path, PutUrl::CONTENT_TYPE, accepts, data)
Request::try_new_put(path, accepts, GetUrl::CONTENT_TYPE, data)
}
}
@@ -213,9 +213,9 @@ where
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, E> {
let string_data = req.try_into_string().await?;
let string_data = req.as_query().unwrap_or_default();
let args = serde_qs::Config::new(5, false)
.deserialize_str::<Self>(&string_data)
.deserialize_str::<Self>(string_data)
.map_err(|e| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;

View File

@@ -568,7 +568,7 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
fn from_server_fn_error(value: ServerFnErrorErr) -> Self;
/// Serializes the custom error type to bytes, according to the encoding given by `Self::Encoding`.
/// Converts the custom error type to a [`String`].
fn ser(&self) -> Bytes {
Self::Encoder::encode(self).unwrap_or_else(|e| {
Self::Encoder::encode(&Self::from_server_fn_error(
@@ -581,7 +581,7 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
})
}
/// Deserializes the custom error type, according to the encoding given by `Self::Encoding`.
/// Deserializes the custom error type from a [`&str`].
fn de(data: Bytes) -> Self {
Self::Encoder::decode(data).unwrap_or_else(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()

View File

@@ -307,18 +307,16 @@ pub trait ServerFn: Send + Sized {
.await
.map(|res| (res, None))
.unwrap_or_else(|e| {
let mut response =
(
<<Self as ServerFn>::Server as crate::Server<
Self::Error,
Self::InputStreamError,
Self::OutputStreamError,
>>::Response::error_response(
Self::PATH, e.ser()
);
let content_type =
<Self::Error as FromServerFnError>::Encoder::CONTENT_TYPE;
response.content_type(content_type);
(response, Some(e))
),
Some(e),
)
});
// if it accepts HTML, we'll redirect to the Referer

View File

@@ -72,10 +72,6 @@ mod axum {
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
// TODO: This does not set the Content-Type on the response. Doing so will
// require a breaking change in order to get the correct encoding from the
// error's `FromServerFnError::Encoder::CONTENT_TYPE` impl.
// Note: This only applies to middleware errors.
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
Response::<Body>::error_response(&path, err)
@@ -153,10 +149,6 @@ mod actix {
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
// TODO: This does not set the Content-Type on the response. Doing so will
// require a breaking change in order to get the correct encoding from the
// error's `FromServerFnError::Encoder::CONTENT_TYPE` impl.
// Note: This only applies to middleware errors.
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
ActixResponse::error_response(&path, err).take()

View File

@@ -5,7 +5,7 @@ use crate::error::{
use actix_web::{
http::{
header,
header::{HeaderValue, CONTENT_TYPE, LOCATION},
header::{HeaderValue, LOCATION},
StatusCode,
},
HttpResponse,
@@ -80,12 +80,6 @@ impl Res for ActixResponse {
))
}
fn content_type(&mut self, content_type: &str) {
if let Ok(content_type) = HeaderValue::from_str(content_type) {
self.0.headers_mut().insert(CONTENT_TYPE, content_type);
}
}
fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
*self.0.status_mut() = StatusCode::FOUND;

View File

@@ -100,13 +100,6 @@ impl Res for Response<Body> {
.unwrap()
}
fn content_type(&mut self, content_type: &str) {
if let Ok(content_type) = HeaderValue::from_str(content_type) {
self.headers_mut()
.insert(header::CONTENT_TYPE, content_type);
}
}
fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
self.headers_mut().insert(header::LOCATION, path);

View File

@@ -60,13 +60,6 @@ impl Res for Response<Body> {
.unwrap()
}
fn content_type(&mut self, content_type: &str) {
if let Ok(content_type) = HeaderValue::from_str(content_type) {
self.headers_mut()
.insert(header::CONTENT_TYPE, content_type);
}
}
fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
self.headers_mut().insert(header::LOCATION, path);

View File

@@ -37,14 +37,9 @@ where
/// Represents the response as created by the server;
pub trait Res {
/// Converts an error into a response, with a `500` status code and the error as its body.
/// Converts an error into a response, with a `500` status code and the error text as its body.
fn error_response(path: &str, err: Bytes) -> Self;
/// Set the `Content-Type` header for the response.
fn content_type(&mut self, #[allow(unused_variables)] content_type: &str) {
// TODO 0.9: remove this method and default implementation. It is only included here
// to allow setting the `Content-Type` header for error responses without requiring a
// semver-incompatible change.
}
/// Redirect the response by setting a 302 code and Location header.
fn redirect(&mut self, path: &str);
}
@@ -108,10 +103,6 @@ impl Res for BrowserMockRes {
unreachable!()
}
fn content_type(&mut self, _content_type: &str) {
unreachable!()
}
fn redirect(&mut self, _path: &str) {
unreachable!()
}

View File

@@ -5,22 +5,16 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "RPC for any web framework."
readme = "../README.md"
version = "0.8.7"
version = { workspace = true }
edition.workspace = true
[dependencies]
quote = { workspace = true, default-features = true }
syn = { features = [
"full",
"parsing",
"extra-traits",
], workspace = true, default-features = true }
syn = { features = ["full", "parsing", "extra-traits"] , workspace = true, default-features = true }
proc-macro2 = { workspace = true, default-features = true }
xxhash-rust = { features = [
"const_xxh64",
], workspace = true, default-features = true }
xxhash-rust = { features = ["const_xxh64"] , workspace = true, default-features = true }
const_format = { workspace = true, default-features = true }
convert_case = { workspace = true, default-features = true }
convert_case = { workspace = true , default-features = true }
[build-dependencies]

View File

@@ -1556,7 +1556,7 @@ impl Parse for ServerFnBody {
impl ServerFnBody {
fn to_dummy_ident(&self) -> Ident {
Ident::new(&format!("__server_{}", self.ident), self.ident.span())
Ident::new(&format!("__{}", self.ident), self.ident.span())
}
fn to_dummy_output(&self) -> TokenStream2 {

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.2.8"
version = "0.2.6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,10 +1,6 @@
use super::{Attribute, NextAttribute};
use crate::{
erased::{Erased, ErasedLocal},
html::attribute::NamedAttributeKey,
renderer::{dom::Element, Rndr},
};
use std::{any::TypeId, fmt::Debug, mem};
use crate::erased::{Erased, ErasedLocal};
use std::{any::TypeId, fmt::Debug};
#[cfg(feature = "ssr")]
use std::{future::Future, pin::Pin};
@@ -29,7 +25,6 @@ pub struct AnyAttribute {
resolve: fn(Erased) -> Pin<Box<dyn Future<Output = AnyAttribute> + Send>>,
#[cfg(feature = "ssr")]
dry_resolve: fn(&mut Erased),
keys: fn(&Erased) -> Vec<NamedAttributeKey>,
}
impl Clone for AnyAttribute {
@@ -49,7 +44,6 @@ pub struct AnyAttributeState {
type_id: TypeId,
state: ErasedLocal,
el: crate::renderer::types::Element,
keys: Vec<NamedAttributeKey>,
}
/// Converts an [`Attribute`] into [`AnyAttribute`].
@@ -90,7 +84,6 @@ where
) -> AnyAttributeState {
AnyAttributeState {
type_id: TypeId::of::<T>(),
keys: value.get_ref::<T>().keys(),
state: ErasedLocal::new(value.into_inner::<T>().build(&el)),
el,
}
@@ -103,7 +96,6 @@ where
) -> AnyAttributeState {
AnyAttributeState {
type_id: TypeId::of::<T>(),
keys: value.get_ref::<T>().keys(),
state: ErasedLocal::new(
value.into_inner::<T>().hydrate::<true>(&el),
),
@@ -118,7 +110,6 @@ where
) -> AnyAttributeState {
AnyAttributeState {
type_id: TypeId::of::<T>(),
keys: value.get_ref::<T>().keys(),
state: ErasedLocal::new(
value.into_inner::<T>().hydrate::<true>(&el),
),
@@ -149,12 +140,6 @@ where
async move {value.into_inner::<T>().resolve().await.into_any_attr()}.boxed()
}
fn keys<T: Attribute + 'static>(
value: &Erased,
) -> Vec<NamedAttributeKey> {
value.get_ref::<T>().keys()
}
let value = self.into_cloneable_owned();
AnyAttribute {
type_id: TypeId::of::<T::CloneableOwned>(),
@@ -173,7 +158,6 @@ where
resolve: resolve::<T::CloneableOwned>,
#[cfg(feature = "ssr")]
dry_resolve: dry_resolve::<T::CloneableOwned>,
keys: keys::<T::CloneableOwned>,
}
}
}
@@ -284,10 +268,6 @@ impl Attribute for AnyAttribute {
enabled."
);
}
fn keys(&self) -> Vec<NamedAttributeKey> {
(self.keys)(&self.value)
}
}
impl NextAttribute for Vec<AnyAttribute> {
@@ -306,7 +286,7 @@ impl Attribute for Vec<AnyAttribute> {
const MIN_LENGTH: usize = 0;
type AsyncOutput = Vec<AnyAttribute>;
type State = (Element, Vec<AnyAttributeState>);
type State = Vec<AnyAttributeState>;
type Cloneable = Vec<AnyAttribute>;
type CloneableOwned = Vec<AnyAttribute>;
@@ -341,19 +321,13 @@ impl Attribute for Vec<AnyAttribute> {
) -> Self::State {
#[cfg(feature = "hydrate")]
if FROM_SERVER {
(
el.clone(),
self.into_iter()
.map(|attr| attr.hydrate::<true>(el))
.collect(),
)
self.into_iter()
.map(|attr| attr.hydrate::<true>(el))
.collect()
} else {
(
el.clone(),
self.into_iter()
.map(|attr| attr.hydrate::<false>(el))
.collect(),
)
self.into_iter()
.map(|attr| attr.hydrate::<false>(el))
.collect()
}
#[cfg(not(feature = "hydrate"))]
{
@@ -366,34 +340,13 @@ impl Attribute for Vec<AnyAttribute> {
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
(
el.clone(),
self.into_iter().map(|attr| attr.build(el)).collect(),
)
self.into_iter().map(|attr| attr.build(el)).collect()
}
fn rebuild(self, state: &mut Self::State) {
let (el, state) = state;
for old in mem::take(state) {
for key in old.keys {
match key {
NamedAttributeKey::InnerHtml => {
Rndr::set_inner_html(&old.el, "");
}
NamedAttributeKey::Property(prop_name) => {
Rndr::set_property(
&old.el,
&prop_name,
&wasm_bindgen::JsValue::UNDEFINED,
);
}
NamedAttributeKey::Attribute(key) => {
Rndr::remove_attribute(&old.el, &key);
}
}
}
for (attr, state) in self.into_iter().zip(state.iter_mut()) {
attr.rebuild(state)
}
*state = self.into_iter().map(|s| s.build(el)).collect();
}
fn into_cloneable(self) -> Self::Cloneable {
@@ -432,8 +385,4 @@ impl Attribute for Vec<AnyAttribute> {
enabled."
);
}
fn keys(&self) -> Vec<NamedAttributeKey> {
self.iter().flat_map(|s| s.keys()).collect()
}
}

View File

@@ -4,7 +4,7 @@ use super::{
use crate::{
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, Attribute,
AttributeValue, NamedAttributeKey,
AttributeValue,
},
view::{add_attr::AddAnyAttr, Position, ToTemplate},
};
@@ -112,12 +112,6 @@ where
value: self.value.resolve().await,
}
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::Attribute(
self.key.as_ref().to_string().into(),
)]
}
}
impl<K, V> NextAttribute for CustomAttr<K, V>

View File

@@ -195,10 +195,6 @@ attributes! {
cols "cols",
/// The `colspan` attribute defines the number of columns a cell should span.
colspan "colspan",
/// The `command` attribute defines the command to be invoked when user clicks the `<button>` element which has `commandfor` attribute specified.
command "command",
/// The `commandfor` attribute defines the id of the element which button is controlling. It is generic version of `popovertarget`.
commandfor "commandfor",
/// The `content` attribute gives the value associated with the http-equiv or name attribute.
content "content",
/// The `contenteditable` attribute indicates whether the element's content is editable.

View File

@@ -15,7 +15,7 @@ pub use key::*;
use maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
};
use std::{borrow::Cow, fmt::Debug, future::Future};
use std::{fmt::Debug, future::Future};
pub use value::*;
/// Defines an attribute: anything that can modify an element.
@@ -75,25 +75,6 @@ pub trait Attribute: NextAttribute + Send {
/// “Resolves” this into a type that is not waiting for any asynchronous data.
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
/// Returns a set of attribute keys, associated with this attribute, if any.
///
/// This is only used to manage the removal of type-erased attributes, when needed.
fn keys(&self) -> Vec<NamedAttributeKey> {
// TODO: remove default implementation in 0.9, or fix this whole approach
// by making it easier to remove attributes
vec![]
}
}
/// An attribute key can be used to remove an attribute from an element.
pub enum NamedAttributeKey {
/// An ordinary attribute.
Attribute(Cow<'static, str>),
/// A DOM property.
Property(Cow<'static, str>),
/// The `inner_html` pseudo-attribute.
InnerHtml,
}
/// Adds another attribute to this one, returning a new attribute.
@@ -152,10 +133,6 @@ impl Attribute for () {
fn dry_resolve(&mut self) {}
async fn resolve(self) -> Self::AsyncOutput {}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![]
}
}
impl NextAttribute for () {
@@ -272,10 +249,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
Attr(self.0, self.1.resolve().await)
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::Attribute(K::KEY.into())]
}
}
impl<K, V> NextAttribute for Attr<K, V>
@@ -380,14 +353,6 @@ macro_rules! impl_attr_for_tuples {
$($ty.resolve()),*
)
}
fn keys(&self) -> Vec<NamedAttributeKey> {
#[allow(non_snake_case)]
let ($first, $($ty,)*) = &self;
let mut buf = $first.keys();
$(buf.extend($ty.keys());)*
buf
}
}
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
@@ -497,14 +462,6 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
$($ty.resolve()),*
)
}
fn keys(&self) -> Vec<NamedAttributeKey> {
#[allow(non_snake_case)]
let ($first, $($ty,)*) = &self;
let mut buf = $first.keys();
$(buf.extend($ty.keys());)*
buf
}
}
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
@@ -581,10 +538,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
(self.0.resolve().await,)
}
fn keys(&self) -> Vec<NamedAttributeKey> {
self.0.keys()
}
}
impl<A> NextAttribute for (A,)

View File

@@ -1,6 +1,6 @@
use super::attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
NamedAttributeKey, NextAttribute,
NextAttribute,
};
use crate::{
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
@@ -97,10 +97,6 @@ where
class: self.class.resolve().await,
}
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::Attribute("class".into())]
}
}
impl<C> NextAttribute for Class<C>

View File

@@ -3,9 +3,7 @@ use super::attribute::{
NextAttribute,
};
use crate::{
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, NamedAttributeKey,
},
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
prelude::AddAnyAttr,
view::{Position, ToTemplate},
};
@@ -162,10 +160,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![]
}
}
impl<T, D, P> NextAttribute for Directive<T, D, P>

View File

@@ -227,7 +227,7 @@ html_self_closing_elements! {
html_elements! {
/// The `<a>` HTML element (or anchor element), with its href attribute, creates a hyperlink to web pages, files, email addresses, locations in the same page, or anything else a URL can address.
a HtmlAnchorElement [download, href, hreflang, ping, referrerpolicy, rel, target, r#type ] true,
a HtmlAnchorElement [download, href, hreflang, ping, rel, target, r#type ] true,
/// The `<abbr>` HTML element represents an abbreviation or acronym; the optional title attribute can provide an expansion or description for the abbreviation. If present, title must contain this full description and nothing else.
abbr HtmlElement [] true,
/// The `<address>` HTML element indicates that the enclosed HTML provides contact information for a person or people, or for an organization.
@@ -249,7 +249,7 @@ html_elements! {
/// The `<body>` HTML element represents the content of an HTML document. There can be only one `<body>` element in a document.
body HtmlBodyElement [] true,
/// The `<button>` HTML element represents a clickable button, used to submit forms or anywhere in a document for accessible, standard button functionality.
button HtmlButtonElement [command, commandfor, disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, r#type, value, popovertarget, popovertargetaction] true,
button HtmlButtonElement [disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, r#type, value, popovertarget, popovertargetaction] true,
/// Use the HTML `<canvas>` element with either the canvas scripting API or the WebGL API to draw graphics and animations.
canvas HtmlCanvasElement [height, width] true,
/// The `<caption>` HTML element specifies the caption (or title) of a table.
@@ -269,7 +269,7 @@ html_elements! {
/// The `<del>` HTML element represents a range of text that has been deleted from a document. This can be used when rendering "track changes" or source code diff information, for example. The ins element can be used for the opposite purpose: to indicate text that has been added to the document.
del HtmlModElement [cite, datetime] true,
/// The `<details>` HTML element creates a disclosure widget in which information is visible only when the widget is toggled into an "open" state. A summary or label must be provided using the summary element.
details HtmlDetailsElement [name, open] true,
details HtmlDetailsElement [open] true,
/// The `<dfn>` HTML element is used to indicate the term being defined within the context of a definition phrase or sentence. The p element, the dt/dd pairing, or the section element which is the nearest ancestor of the `<dfn>` is considered to be the definition of the term.
dfn HtmlElement [] true,
/// The `<dialog>` HTML element represents a dialog box or other interactive component, such as a dismissible alert, inspector, or subwindow.

View File

@@ -4,7 +4,7 @@ use crate::{
maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
},
Attribute, NamedAttributeKey, NextAttribute,
Attribute, NextAttribute,
},
renderer::Rndr,
view::add_attr::AddAnyAttr,
@@ -105,10 +105,6 @@ where
value: self.value.resolve().await,
}
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::InnerHtml]
}
}
impl<T> NextAttribute for InnerHtml<T>

View File

@@ -329,8 +329,6 @@ where
fn build(self) -> Self::State {
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
let attrs = self.attributes.build(&el);
let children = if E::SELF_CLOSING {
None
} else {
@@ -339,6 +337,8 @@ where
Some(children)
};
let attrs = self.attributes.build(&el);
ElementState {
el,
attrs,

View File

@@ -1,7 +1,6 @@
use crate::{
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, Attribute,
NamedAttributeKey,
},
renderer::{CastFrom, RemoveEventHandler, Rndr},
view::{Position, ToTemplate},
@@ -111,8 +110,6 @@ where
{
On {
event,
#[cfg(feature = "reactive_graph")]
owner: reactive_graph::owner::Owner::current().unwrap_or_default(),
cb: Some(SendWrapper::new(cb)),
}
}
@@ -138,8 +135,6 @@ where
/// An [`Attribute`] that adds an event listener to an element.
pub struct On<E, F> {
event: E,
#[cfg(feature = "reactive_graph")]
owner: reactive_graph::owner::Owner,
cb: Option<SendWrapper<F>>,
}
@@ -151,8 +146,6 @@ where
fn clone(&self) -> Self {
Self {
event: self.event.clone(),
#[cfg(feature = "reactive_graph")]
owner: self.owner.clone(),
cb: self.cb.clone(),
}
}
@@ -200,10 +193,6 @@ where
let _tracing_guard = span.enter();
let ev = E::EventType::from(ev);
#[cfg(feature = "reactive_graph")]
self.owner.with(|| cb.invoke(ev));
#[cfg(not(feature = "reactive_graph"))]
cb.invoke(ev);
}) as Box<dyn FnMut(crate::renderer::types::Event)>;
@@ -243,10 +232,6 @@ where
let _tracing_guard = span.enter();
let ev = E::EventType::from(ev);
#[cfg(feature = "reactive_graph")]
self.owner.with(|| cb.invoke(ev));
#[cfg(not(feature = "reactive_graph"))]
cb.invoke(ev);
}) as Box<dyn FnMut(crate::renderer::types::Event)>;
@@ -323,9 +308,7 @@ where
fn rebuild(self, state: &mut Self::State) {
let (el, prev_cleanup) = state;
if let Some(prev) = prev_cleanup.take() {
if let Some(remove) = prev.into_inner() {
remove();
}
(prev.into_inner())(el);
}
*prev_cleanup = Some(if E::CAPTURE {
self.attach_capture(el)
@@ -337,8 +320,6 @@ where
fn into_cloneable(self) -> Self::Cloneable {
On {
cb: self.cb.map(|cb| SendWrapper::new(cb.take().into_shared())),
#[cfg(feature = "reactive_graph")]
owner: self.owner,
event: self.event,
}
}
@@ -346,8 +327,6 @@ where
fn into_cloneable_owned(self) -> Self::CloneableOwned {
On {
cb: self.cb.map(|cb| SendWrapper::new(cb.take().into_shared())),
#[cfg(feature = "reactive_graph")]
owner: self.owner,
event: self.event,
}
}
@@ -363,10 +342,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![]
}
}
impl<E, F> NextAttribute for On<E, F>

View File

@@ -7,10 +7,7 @@ use super::{
};
use crate::{
html::{
attribute::{
maybe_next_attr_erasure_macros::next_attr_combine,
NamedAttributeKey,
},
attribute::maybe_next_attr_erasure_macros::next_attr_combine,
element::HtmlElement,
},
prelude::Render,
@@ -115,10 +112,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![]
}
}
impl<E, C> NextAttribute for NodeRefAttr<E, C>

View File

@@ -3,9 +3,7 @@ use super::attribute::{
NextAttribute,
};
use crate::{
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, NamedAttributeKey,
},
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
renderer::Rndr,
view::{Position, ToTemplate},
};
@@ -126,12 +124,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::Property(
self.key.as_ref().to_string().into(),
)]
}
}
impl<K, P> NextAttribute for Property<K, P>
@@ -210,7 +202,7 @@ macro_rules! prop_type {
key: &str,
) -> Self::State {
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -220,14 +212,14 @@ macro_rules! prop_type {
key: &str,
) -> Self::State {
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -253,7 +245,7 @@ macro_rules! prop_type {
let was_some = self.is_some();
let value = self.into();
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -266,7 +258,7 @@ macro_rules! prop_type {
let was_some = self.is_some();
let value = self.into();
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -274,7 +266,7 @@ macro_rules! prop_type {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = self.into();
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -302,7 +294,7 @@ macro_rules! prop_type_str {
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -312,14 +304,14 @@ macro_rules! prop_type_str {
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(&*self);
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -347,7 +339,7 @@ macro_rules! prop_type_str {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -360,7 +352,7 @@ macro_rules! prop_type_str {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -368,7 +360,7 @@ macro_rules! prop_type_str {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -400,7 +392,7 @@ impl IntoProperty for Arc<str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
@@ -410,14 +402,14 @@ impl IntoProperty for Arc<str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -443,7 +435,7 @@ impl IntoProperty for Option<Arc<str>> {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -456,7 +448,7 @@ impl IntoProperty for Option<Arc<str>> {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -464,7 +456,7 @@ impl IntoProperty for Option<Arc<str>> {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
Rndr::set_property_or_value(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}

View File

@@ -5,9 +5,7 @@ use super::attribute::{
#[cfg(all(feature = "nightly", rustc_nightly))]
use crate::view::static_types::Static;
use crate::{
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, NamedAttributeKey,
},
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
renderer::{dom::CssStyleDeclaration, Rndr},
view::{Position, ToTemplate},
};
@@ -102,10 +100,6 @@ where
style: self.style.resolve().await,
}
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::Attribute("style".into())]
}
}
impl<S> NextAttribute for Style<S>

View File

@@ -10,9 +10,6 @@
all(feature = "nightly", rustc_nightly),
feature(unsized_const_params)
)]
// support for const generic &'static str has now moved back and forth between
// these two features a couple times; we'll just enable both
#![cfg_attr(all(feature = "nightly", rustc_nightly), feature(adt_const_params))]
#![deny(missing_docs)]
/// Commonly-used traits.

View File

@@ -2,7 +2,6 @@ use crate::{
html::{
attribute::{any_attribute::AnyAttribute, AttributeValue},
class::IntoClass,
property::IntoProperty,
style::IntoStyle,
},
hydration::Cursor,
@@ -12,7 +11,6 @@ use crate::{
view::{strings::StrState, Position, PositionState, ToTemplate},
};
use oco_ref::Oco;
use wasm_bindgen::JsValue;
/// Retained view state for [`Oco`].
pub struct OcoStrState {
@@ -250,47 +248,6 @@ impl IntoClass for Oco<'static, str> {
}
}
impl IntoProperty for Oco<'static, str> {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Self;
type CloneableOwned = Self;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
(el.clone(), value)
}
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from_str(self.as_ref());
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
}
impl IntoStyle for Oco<'static, str> {
type AsyncOutput = Self;
type State = (crate::renderer::types::Element, Self);

View File

@@ -5,8 +5,7 @@ use crate::{
maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
},
Attribute, AttributeKey, AttributeValue, NamedAttributeKey,
NextAttribute,
Attribute, AttributeKey, AttributeValue, NextAttribute,
},
event::{change, input, on},
property::{prop, IntoProperty},
@@ -258,9 +257,7 @@ where
prop(self.key(), signal).rebuild(attr_state);
if let Some(prev) = prev_cleanup.take() {
if let Some(remove) = prev.into_inner() {
remove();
}
(prev.into_inner())(el);
}
*prev_cleanup = Some(self.attach(el));
}
@@ -278,10 +275,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![]
}
}
impl<Key, T, R, W> NextAttribute for Bind<Key, T, R, W>

View File

@@ -153,20 +153,6 @@ where
OwnedViewState::new(state, self.owner)
}
async fn hydrate_async(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let state = self
.owner
.with(|| {
ScopedFuture::new(self.view.hydrate_async(cursor, position))
})
.await;
OwnedViewState::new(state, self.owner)
}
async fn resolve(self) -> Self::AsyncOutput {
let OwnedView { owner, view } = self;
let view = owner

View File

@@ -36,46 +36,6 @@ pub type ClassList = web_sys::DomTokenList;
pub type CssStyleDeclaration = web_sys::CssStyleDeclaration;
pub type TemplateElement = web_sys::HtmlTemplateElement;
/// A microtask is a short function which will run after the current task has
/// completed its work and when there is no other code waiting to be run before
/// control of the execution context is returned to the browser's event loop.
///
/// Microtasks are especially useful for libraries and frameworks that need
/// to perform final cleanup or other just-before-rendering tasks.
///
/// [MDN queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
pub fn queue_microtask(task: impl FnOnce() + 'static) {
use js_sys::{Function, Reflect};
let task = Closure::once_into_js(task);
let window = window();
let queue_microtask =
Reflect::get(&window, &JsValue::from_str("queueMicrotask"))
.expect("queueMicrotask not available");
let queue_microtask = queue_microtask.unchecked_into::<Function>();
_ = queue_microtask.call1(&JsValue::UNDEFINED, &task);
}
fn queue(fun: Box<dyn FnOnce()>) {
use std::cell::{Cell, RefCell};
thread_local! {
static PENDING: Cell<bool> = const { Cell::new(false) };
static QUEUE: RefCell<Vec<Box<dyn FnOnce()>>> = RefCell::new(Vec::new());
}
QUEUE.with_borrow_mut(|q| q.push(fun));
if !PENDING.replace(true) {
queue_microtask(|| {
let tasks = QUEUE.take();
for task in tasks {
task();
}
PENDING.set(false);
})
}
}
impl Dom {
pub fn intern(text: &str) -> &str {
intern(text)
@@ -251,20 +211,6 @@ impl Dom {
}
}
pub fn set_property_or_value(el: &Element, key: &str, value: &JsValue) {
if key == "value" {
queue(Box::new({
let el = el.clone();
let value = value.clone();
move || {
Self::set_property(&el, "value", &value);
}
}))
} else {
Self::set_property(el, key, value);
}
}
pub fn set_property(el: &Element, key: &str, value: &JsValue) {
or_debug!(
js_sys::Reflect::set(
@@ -296,20 +242,19 @@ impl Dom {
// return the remover
RemoveEventHandler::new({
let name = name.to_owned();
let el = el.clone();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let cb = send_wrapper::SendWrapper::new(move || {
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
or_debug!(
el.remove_event_listener_with_callback(
intern(&name),
cb.as_ref().unchecked_ref()
),
&el,
el,
"removeEventListener"
)
});
move || cb()
}
})
}
@@ -335,21 +280,20 @@ impl Dom {
// return the remover
RemoveEventHandler::new({
let name = name.to_owned();
let el = el.clone();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let cb = send_wrapper::SendWrapper::new(move || {
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
or_debug!(
el.remove_event_listener_with_callback_and_bool(
intern(&name),
cb.as_ref().unchecked_ref(),
true
),
&el,
el,
"removeEventListener"
)
});
move || cb()
}
})
}
@@ -450,19 +394,17 @@ impl Dom {
// return the remover
RemoveEventHandler::new({
let key = key.to_owned();
let el = el.clone();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let el_cb = send_wrapper::SendWrapper::new((el, cb));
move || {
let (el, cb) = el_cb.take();
drop(cb);
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
drop(cb.take());
or_debug!(
js_sys::Reflect::delete_property(
&el,
el,
&JsValue::from_str(&key)
),
&el,
el,
"delete property"
);
}

View File

@@ -1,5 +1,5 @@
use crate::view::{Mountable, ToTemplate};
use std::{borrow::Cow, fmt::Debug, marker::PhantomData};
use std::{borrow::Cow, fmt::Debug};
use wasm_bindgen::JsValue;
/// A DOM renderer.
@@ -120,33 +120,16 @@ pub trait Renderer: Send + Sized + Debug + 'static {
should store it in some other data structure to clean it up \
later to avoid dropping it immediately, or leak it with \
std::mem::forget() to never drop it."]
#[allow(clippy::type_complexity)]
pub struct RemoveEventHandler<T>(
Option<Box<dyn FnOnce() + Send + Sync>>,
// only here to keep the generic, removing which would be a breaking change
// TODO remove generic in 0.9
PhantomData<fn() -> T>,
);
pub struct RemoveEventHandler<T>(Box<dyn FnOnce(&T) + Send + Sync>);
impl<T> RemoveEventHandler<T> {
/// Creates a new container with a function that will be called when it is dropped.
pub(crate) fn new(remove: impl FnOnce() + Send + Sync + 'static) -> Self {
Self(Some(Box::new(remove)), PhantomData)
pub(crate) fn new(remove: impl FnOnce(&T) + Send + Sync + 'static) -> Self {
Self(Box::new(remove))
}
#[allow(clippy::type_complexity)]
pub(crate) fn into_inner(
mut self,
) -> Option<Box<dyn FnOnce() + Send + Sync>> {
self.0.take()
}
}
impl<T> Drop for RemoveEventHandler<T> {
fn drop(&mut self) {
if let Some(cb) = self.0.take() {
cb()
}
pub(crate) fn into_inner(self) -> Box<dyn FnOnce(&T) + Send + Sync> {
self.0
}
}

View File

@@ -699,15 +699,7 @@ impl Render for AnyViewWithAttrs {
fn rebuild(self, state: &mut Self::State) {
self.view.rebuild(&mut state.view);
let elements = state.elements();
// FIXME this seems wrong but I think the previous version was also broken!
if let Some(element) = elements.first() {
self.attrs.rebuild(&mut (
element.clone(),
std::mem::take(&mut state.attrs),
));
}
self.attrs.rebuild(&mut state.attrs);
}
}
@@ -828,7 +820,7 @@ impl AddAnyAttr for AnyViewWithAttrs {
}
}
/// State for any view with attributes spread onto it.
/// wip
pub struct AnyViewWithAttrsState {
view: AnyViewState,
attrs: Vec<AnyAttributeState>,

View File

@@ -3,10 +3,7 @@ use super::{
Render, RenderHtml,
};
use crate::{
html::attribute::{
any_attribute::AnyAttribute, Attribute, NamedAttributeKey,
NextAttribute,
},
html::attribute::{any_attribute::AnyAttribute, Attribute, NextAttribute},
hydration::Cursor,
ssr::StreamBuilder,
};
@@ -267,13 +264,6 @@ where
Either::Right(right) => Either::Right(right.resolve().await),
}
}
fn keys(&self) -> Vec<NamedAttributeKey> {
match self {
Either::Left(left) => left.keys(),
Either::Right(right) => right.keys(),
}
}
}
impl<A, B> RenderHtml for Either<A, B>

View File

@@ -8,8 +8,7 @@ use crate::{
maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
},
Attribute, AttributeKey, AttributeValue, NamedAttributeKey,
NextAttribute,
Attribute, AttributeKey, AttributeValue, NextAttribute,
},
hydration::Cursor,
renderer::{CastFrom, Rndr},
@@ -112,10 +111,6 @@ where
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn keys(&self) -> Vec<NamedAttributeKey> {
vec![NamedAttributeKey::Attribute(K::KEY.into())]
}
}
impl<K, const V: &'static str> NextAttribute for StaticAttr<K, V>

View File

@@ -1,6 +1,6 @@
[package]
name = "wasm_split_helpers"
version = "0.1.2"
version = "0.1.1"
authors = ["Greg Johnston"]
license = "MIT"
readme = "README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "wasm_split_macros"
version = "0.1.3"
version = "0.1.2"
authors = ["Greg Johnston"]
license = "MIT"
readme = "README.md"

View File

@@ -85,7 +85,6 @@ pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
import_sig.output = async_output;
}
let wrapper_pub = item_fn.vis;
let mut wrapper_sig = item_fn.sig;
wrapper_sig.asyncness = Some(Default::default());
let mut args = Vec::new();
@@ -148,8 +147,7 @@ pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
}
#[allow(non_snake_case)]
#(#attrs)*
#wrapper_pub #wrapper_sig {
#wrapper_sig {
#(#attrs)*
#[allow(improper_ctypes_definitions)]
#[allow(non_snake_case)]
@@ -164,7 +162,7 @@ pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
#[doc(hidden)]
#[allow(non_snake_case)]
#wrapper_pub async fn #preload_name() {
pub async fn #preload_name() {
::leptos::wasm_split_helpers::ensure_loaded(&#split_loader_ident).await.unwrap();
}
}

View File

@@ -0,0 +1,503 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"compiler_builtins",
"gimli",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "alloc"
version = "0.0.0"
dependencies = [
"compiler_builtins",
"core",
]
[[package]]
name = "alloctests"
version = "0.0.0"
dependencies = [
"rand",
"rand_xorshift",
]
[[package]]
name = "cc"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "compiler_builtins"
version = "0.1.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164cdc689e4c6d69417f77a5f48be240c291e84fbef0b1281755dc754b19c809"
dependencies = [
"cc",
"rustc-std-workspace-core",
]
[[package]]
name = "core"
version = "0.0.0"
[[package]]
name = "coretests"
version = "0.0.0"
dependencies = [
"rand",
"rand_xorshift",
]
[[package]]
name = "dlmalloc"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cff88b751e7a276c4ab0e222c3f355190adc6dde9ce39c851db39da34990df7"
dependencies = [
"cfg-if",
"compiler_builtins",
"libc",
"rustc-std-workspace-core",
"windows-sys",
]
[[package]]
name = "fortanix-sgx-abi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57cafc2274c10fab234f176b25903ce17e690fca7597090d50880e047a0389c5"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"rustc-std-workspace-core",
"rustc-std-workspace-std",
"unicode-width",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "hashbrown"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "hermit-abi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
dependencies = [
"rustc-std-workspace-core",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
"compiler_builtins",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"compiler_builtins",
"memchr",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "panic_abort"
version = "0.0.0"
dependencies = [
"alloc",
"cfg-if",
"compiler_builtins",
"core",
"libc",
]
[[package]]
name = "panic_unwind"
version = "0.0.0"
dependencies = [
"alloc",
"cfg-if",
"compiler_builtins",
"core",
"libc",
"unwind",
]
[[package]]
name = "proc_macro"
version = "0.0.0"
dependencies = [
"core",
"rustc-literal-escaper",
"std",
]
[[package]]
name = "profiler_builtins"
version = "0.0.0"
dependencies = [
"cc",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "r-efi-alloc"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e43c53ff1a01d423d1cb762fd991de07d32965ff0ca2e4f80444ac7804198203"
dependencies = [
"compiler_builtins",
"r-efi",
"rustc-std-workspace-core",
]
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]
[[package]]
name = "rustc-literal-escaper"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0041b6238913c41fe704213a4a9329e2f685a156d1781998128b4149c230ad04"
dependencies = [
"rustc-std-workspace-std",
]
[[package]]
name = "rustc-std-workspace-alloc"
version = "1.99.0"
dependencies = [
"alloc",
]
[[package]]
name = "rustc-std-workspace-core"
version = "1.99.0"
dependencies = [
"core",
]
[[package]]
name = "rustc-std-workspace-std"
version = "1.99.0"
dependencies = [
"std",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "std"
version = "0.0.0"
dependencies = [
"addr2line",
"alloc",
"cfg-if",
"compiler_builtins",
"core",
"dlmalloc",
"fortanix-sgx-abi",
"hashbrown",
"hermit-abi",
"libc",
"miniz_oxide",
"object",
"panic_abort",
"panic_unwind",
"r-efi",
"r-efi-alloc",
"rand",
"rand_xorshift",
"rustc-demangle",
"std_detect",
"unwind",
"wasi",
"windows-targets 0.0.0",
]
[[package]]
name = "std_detect"
version = "0.1.5"
dependencies = [
"cfg-if",
"compiler_builtins",
"libc",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "sysroot"
version = "0.0.0"
dependencies = [
"proc_macro",
"profiler_builtins",
"std",
"test",
]
[[package]]
name = "test"
version = "0.0.0"
dependencies = [
"core",
"getopts",
"libc",
"std",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
"rustc-std-workspace-std",
]
[[package]]
name = "unwind"
version = "0.0.0"
dependencies = [
"cfg-if",
"compiler_builtins",
"core",
"libc",
"unwinding",
]
[[package]]
name = "unwinding"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8393f2782b6060a807337ff353780c1ca15206f9ba2424df18cb6e733bd7b345"
dependencies = [
"compiler_builtins",
"gimli",
"rustc-std-workspace-core",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-alloc",
"rustc-std-workspace-core",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.0.0"
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

Some files were not shown because too many files have changed in this diff Show More