Compare commits

...

18 Commits
4239 ... v0.8.8

Author SHA1 Message Date
Greg Johnston
b54f80f529 chore: publish new patch releases for changed packages 2025-08-26 17:13:21 -04:00
Greg Johnston
a48a2994ee Merge pull request #4255 from leptos-rs/4254
Revert recent broken changes
2025-08-26 17:09:01 -04:00
Greg Johnston
aedcd5148c Revert "feat: add default "auto" live reload protocol option (#4224)"
This reverts commit a97eceacf1.
2025-08-26 13:25:49 -04:00
Greg Johnston
9160d8aaa6 Revert "made <Show> accept signals in addition to closures (#4236)"
This reverts commit db9f323f8d.
2025-08-26 13:24:44 -04:00
Greg Johnston
274fe07dae Merge remote-tracking branch 'origin' 2025-08-26 13:23:17 -04:00
Greg Johnston
7add26fc41 docs: correctly document additional serialization features for leptos_server (#4250) 2025-08-25 20:46:40 -04:00
Greg Johnston
d9213850f7 chore: publish new patch releases for changed packages 2025-08-25 20:40:32 -04:00
Marc-Stefan Cassola
db9f323f8d made <Show> accept signals in addition to closures (#4236) 2025-08-25 14:50:20 -07:00
Greg Johnston
1d0f668dc3 Merge pull request #4235 from leptos-rs/4217
Special-case `value` property to support quirky `<select>` behavior
2025-08-25 09:05:29 -04:00
Gabriel Lopes Veiga
a97eceacf1 feat: add default "auto" live reload protocol option (#4224)
* feat: add default "auto" live reload protocol option

* fix: make "auto" live reload protocol option last
2025-08-23 17:29:22 -07:00
Greg Johnston
3d6ea6d285 Merge pull request #4242 from leptos-rs/4239
Fixes for two server function issues
2025-08-22 21:00:54 -04:00
Greg Johnston
99c3d8f9e9 fix: correctly parse unquoted text with punctuation in stable (closes #4137) (#4238) 2025-08-22 21:00:39 -04:00
Greg Johnston
f50adc00bc chore: rename changed method to avoid breaking user code that called set_property 2025-08-21 19:47:02 -04:00
Greg Johnston
1340deee96 chore: typo in name of feature test 2025-08-21 19:20:41 -04:00
Greg Johnston
8da3011a7f fix: special-case value prop so that it waits for children, if any, before being set (closes #4217) 2025-08-20 22:07:32 -04:00
Greg Johnston
959677f018 chore: move queue_microtask implementation into tachys 2025-08-20 22:06:38 -04:00
Greg Johnston
03529b3992 chore: add regression test for #4005 2025-08-20 21:19:35 -04:00
Greg Johnston
8bfd0ce143 chore: add regression test for #4217 2025-08-20 21:11:16 -04:00
31 changed files with 259 additions and 70 deletions

26
Cargo.lock generated
View File

@@ -1788,7 +1788,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "leptos"
version = "0.8.6"
version = "0.8.8"
dependencies = [
"any_spawner",
"base64",
@@ -1869,7 +1869,7 @@ dependencies = [
[[package]]
name = "leptos_axum"
version = "0.8.5"
version = "0.8.6"
dependencies = [
"any_spawner",
"axum",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "leptos_config"
version = "0.8.5"
version = "0.8.7"
dependencies = [
"config",
"regex",
@@ -1906,7 +1906,7 @@ dependencies = [
[[package]]
name = "leptos_dom"
version = "0.8.5"
version = "0.8.6"
dependencies = [
"js-sys",
"leptos",
@@ -1952,7 +1952,7 @@ dependencies = [
[[package]]
name = "leptos_macro"
version = "0.8.6"
version = "0.8.7"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1972,7 +1972,7 @@ dependencies = [
"rustc_version",
"serde",
"server_fn",
"server_fn_macro 0.8.6",
"server_fn_macro 0.8.7",
"syn 2.0.104",
"tracing",
"trybuild",
@@ -1996,7 +1996,7 @@ dependencies = [
[[package]]
name = "leptos_router"
version = "0.8.5"
version = "0.8.6"
dependencies = [
"any_spawner",
"either_of",
@@ -2746,7 +2746,7 @@ dependencies = [
[[package]]
name = "reactive_graph"
version = "0.2.5"
version = "0.2.6"
dependencies = [
"any_spawner",
"async-lock",
@@ -2789,7 +2789,7 @@ dependencies = [
[[package]]
name = "reactive_stores_macro"
version = "0.2.5"
version = "0.2.6"
dependencies = [
"convert_case 0.8.0",
"proc-macro-error2",
@@ -3283,7 +3283,7 @@ dependencies = [
[[package]]
name = "server_fn"
version = "0.8.5"
version = "0.8.6"
dependencies = [
"actix-web",
"actix-ws",
@@ -3346,7 +3346,7 @@ dependencies = [
[[package]]
name = "server_fn_macro"
version = "0.8.6"
version = "0.8.7"
dependencies = [
"const_format",
"convert_case 0.8.0",
@@ -3361,7 +3361,7 @@ dependencies = [
name = "server_fn_macro_default"
version = "0.8.5"
dependencies = [
"server_fn_macro 0.8.6",
"server_fn_macro 0.8.7",
"syn 2.0.104",
]
@@ -3573,7 +3573,7 @@ dependencies = [
[[package]]
name = "tachys"
version = "0.2.6"
version = "0.2.7"
dependencies = [
"any_spawner",
"async-trait",

View File

@@ -50,26 +50,26 @@ 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.6" }
leptos_config = { path = "./leptos_config", version = "0.8.5" }
leptos_dom = { path = "./leptos_dom", version = "0.8.5" }
leptos = { path = "./leptos", version = "0.8.8" }
leptos_config = { path = "./leptos_config", version = "0.8.7" }
leptos_dom = { path = "./leptos_dom", version = "0.8.6" }
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.6" }
leptos_router = { path = "./router", version = "0.8.5" }
leptos_macro = { path = "./leptos_macro", version = "0.8.7" }
leptos_router = { path = "./router", version = "0.8.6" }
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.5" }
reactive_graph = { path = "./reactive_graph", version = "0.2.6" }
reactive_stores = { path = "./reactive_stores", version = "0.2.5" }
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.6" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.6" }
server_fn = { path = "./server_fn", version = "0.8.6" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.7" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.5" }
tachys = { path = "./tachys", version = "0.2.6" }
tachys = { path = "./tachys", version = "0.2.7" }
wasm_split_helpers = { path = "./wasm_split", version = "0.1.2" }
wasm_split_macros = { path = "./wasm_split_macros", version = "0.1.2" }

View File

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

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

@@ -18,3 +18,28 @@ 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,3 +25,21 @@ 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,4 +1,7 @@
use crate::{issue_4088::Routes4088, pr_4015::Routes4015, pr_4091::Routes4091};
use crate::{
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
pr_4015::Routes4015, pr_4091::Routes4091,
};
use leptos::prelude::*;
use leptos_meta::{MetaTags, *};
use leptos_router::{
@@ -37,6 +40,8 @@ pub fn App() -> impl IntoView {
<Routes4091/>
<Routes4015/>
<Routes4088/>
<Routes4217/>
<Routes4005/>
</Routes>
</main>
</Router>
@@ -59,6 +64,8 @@ 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>
</ul>
</nav>
}

View File

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

@@ -0,0 +1,24 @@
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,5 +1,7 @@
pub mod app;
mod issue_4005;
mod issue_4088;
mod issue_4217;
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 = "Axum integrations for the Leptos web framework."
version = "0.8.5"
version = "0.8.6"
rust-version.workspace = true
edition.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos"
version = "0.8.6"
version = "0.8.8"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

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.5"
version = "0.8.7"
rust-version.workspace = true
edition.workspace = true

View File

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

View File

@@ -258,15 +258,7 @@ 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) {
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);
tachys::renderer::dom::queue_microtask(task);
}
/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.8.6"
version = "0.8.7"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -24,7 +24,9 @@ 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 }
rstml = { workspace = true, default-features = true }
rstml = { workspace = true, default-features = true, features = [
"rawtext-stable-hack",
] }
leptos_hot_reload = { workspace = true }
server_fn_macro = { workspace = true }
convert_case = { workspace = true, default-features = true }

View File

@@ -323,7 +323,14 @@ fn view_macro_impl(tokens: TokenStream, template: bool) -> TokenStream {
.chain(tokens)
.collect()
};
let config = rstml::ParserConfig::default().recover_block(true);
let macro_call_pattern = if let Some(class) = &global_class {
quote!(view! { class = #class, %% })
} else {
quote!(view! {%%})
};
let config = rstml::ParserConfig::default()
.recover_block(true)
.macro_call_pattern(macro_call_pattern);
let parser = rstml::Parser::new(config);
let (mut nodes, errors) = parser.parse_recoverable(tokens).split_vec();
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());

View File

@@ -44,7 +44,8 @@ denylist = ["tracing"]
max_combination_size = 2
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
rustdoc-args = ["--generate-link-to-definition", "--cfg", "docsrs"]
all-features = true
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }

View File

@@ -386,6 +386,7 @@ 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,
@@ -418,6 +419,7 @@ 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,
@@ -451,6 +453,7 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> ArcOnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
@@ -484,6 +487,7 @@ 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,
@@ -748,6 +752,7 @@ 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,
@@ -780,6 +785,7 @@ 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,
@@ -813,6 +819,7 @@ where
}
#[cfg(feature = "serde-lite")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-lite")))]
impl<T> OnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
@@ -846,6 +853,7 @@ 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,6 +709,7 @@ where
}
#[cfg(feature = "rkyv")]
#[cfg_attr(docsrs, doc(cfg(feature = "rkyv")))]
impl<T> ArcResource<T, RkyvCodec>
where
RkyvCodec: Encoder<T> + Decoder<T>,
@@ -1048,6 +1049,7 @@ 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>,
@@ -1105,6 +1107,7 @@ where
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> Resource<T, MiniserdeCodec>
where
MiniserdeCodec: Encoder<T> + Decoder<T>,
@@ -1164,6 +1167,7 @@ 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>,
@@ -1222,6 +1226,7 @@ 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,6 +80,7 @@ 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>,
@@ -102,6 +103,7 @@ 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>,
@@ -124,6 +126,7 @@ where
}
#[cfg(feature = "miniserde")]
#[cfg_attr(docsrs, doc(cfg(feature = "miniserde")))]
impl<T> SharedValue<T, MiniserdeCodec>
where
MiniserdeCodec: Encoder<T> + Decoder<T>,
@@ -146,6 +149,7 @@ 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 = "reactive_graph"
version = "0.2.5"
version = "0.2.6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.8.5"
version = "0.8.6"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

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.5"
version = "0.8.6"
rust-version.workspace = true
edition.workspace = true

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.6"
version = "0.8.7"
edition.workspace = true
[dependencies]

View File

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

View File

@@ -329,6 +329,8 @@ 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 {
@@ -337,8 +339,6 @@ where
Some(children)
};
let attrs = self.attributes.build(&el);
ElementState {
el,
attrs,

View File

@@ -202,7 +202,7 @@ macro_rules! prop_type {
key: &str,
) -> Self::State {
let value = self.into();
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
(el.clone(), value)
}
@@ -212,14 +212,14 @@ macro_rules! prop_type {
key: &str,
) -> Self::State {
let value = self.into();
Rndr::set_property(el, key, &value);
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 = self.into();
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}
@@ -245,7 +245,7 @@ macro_rules! prop_type {
let was_some = self.is_some();
let value = self.into();
if was_some {
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
}
(el.clone(), value)
}
@@ -258,7 +258,7 @@ macro_rules! prop_type {
let was_some = self.is_some();
let value = self.into();
if was_some {
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
}
(el.clone(), value)
}
@@ -266,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}
@@ -294,7 +294,7 @@ macro_rules! prop_type_str {
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
(el.clone(), value)
}
@@ -304,14 +304,14 @@ macro_rules! prop_type_str {
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
Rndr::set_property(el, key, &value);
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(&*self);
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}
@@ -339,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
}
(el.clone(), value)
}
@@ -352,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
}
(el.clone(), value)
}
@@ -360,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}
@@ -392,7 +392,7 @@ impl IntoProperty for Arc<str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
(el.clone(), value)
}
@@ -402,14 +402,14 @@ impl IntoProperty for Arc<str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property(el, key, &value);
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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}
@@ -435,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
}
(el.clone(), value)
}
@@ -448,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
}
(el.clone(), value)
}
@@ -456,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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}

View File

@@ -261,7 +261,7 @@ impl IntoProperty for Oco<'static, str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
(el.clone(), value)
}
@@ -271,14 +271,14 @@ impl IntoProperty for Oco<'static, str> {
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
Rndr::set_property(el, key, &value);
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(el, key, &value);
Rndr::set_property_or_value(el, key, &value);
*prev = value;
}

View File

@@ -36,6 +36,46 @@ 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)
@@ -211,6 +251,20 @@ 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(