mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
impl all the macro helper traits for (Scope, T)
This commit is contained in:
parent
13f8006162
commit
59f753cebb
6 changed files with 379 additions and 571 deletions
|
@ -25,7 +25,7 @@ fn main() {
|
|||
mount_to_body(view_fn);
|
||||
}
|
||||
|
||||
fn view_fn(cx: Scope) -> impl IntoNode {
|
||||
fn view_fn(cx: Scope) -> impl IntoView {
|
||||
let (tick, set_tick) = create_signal(cx, 0);
|
||||
let (count, set_count) = create_signal(cx, 0);
|
||||
let (show, set_show) = create_signal(cx, true);
|
||||
|
@ -80,58 +80,50 @@ fn view_fn(cx: Scope) -> impl IntoNode {
|
|||
|
||||
[
|
||||
h1(cx)
|
||||
.dyn_child(move || text(count().to_string()))
|
||||
.into_node(cx),
|
||||
.child(cx, move || text(count().to_string()))
|
||||
.into_view(cx),
|
||||
button(cx)
|
||||
.on(ev::click, move |_| set_tick.update(|t| *t += 1))
|
||||
.child(text("Tick"))
|
||||
.into_node(cx),
|
||||
.child(cx, text("Tick"))
|
||||
.into_view(cx),
|
||||
button(cx)
|
||||
.on(ev::click, move |_| set_count.update(|n| *n += 1))
|
||||
.child(text("Click me"))
|
||||
.into_node(cx),
|
||||
.child(cx, text("Click me"))
|
||||
.into_view(cx),
|
||||
button(cx)
|
||||
.on(ev::Undelegated(ev::click), move |_| {
|
||||
set_count.update(|n| *n += 1)
|
||||
})
|
||||
.child(text("Click me (undelegated)"))
|
||||
.into_node(cx),
|
||||
.child(cx, text("Click me (undelegated)"))
|
||||
.into_view(cx),
|
||||
pre(cx)
|
||||
.child(EachKey::new(
|
||||
iterable,
|
||||
|i| *i,
|
||||
move |i| text(format!("{i}, ")),
|
||||
))
|
||||
.into_node(cx),
|
||||
.child(
|
||||
cx,
|
||||
EachKey::new(iterable, |i| *i, move |i| text(format!("{i}, "))),
|
||||
)
|
||||
.into_view(cx),
|
||||
pre(cx)
|
||||
.child(text("0, 1, 2, 3, 4, 5, 6, 7, 8, 9"))
|
||||
.into_node(cx),
|
||||
.child(cx, text("0, 1, 2, 3, 4, 5, 6, 7, 8, 9"))
|
||||
.into_view(cx),
|
||||
input(cx)
|
||||
.class("input input-primary")
|
||||
.dyn_class(move || {
|
||||
if apply_default_class_set() {
|
||||
Some("a b")
|
||||
} else {
|
||||
Some("b c")
|
||||
}
|
||||
})
|
||||
.dyn_attr("disabled", move || disabled().then_some(""))
|
||||
.into_node(cx),
|
||||
MyComponent.into_node(cx),
|
||||
.class(cx, "input input-primary", true)
|
||||
.attr(cx, "disabled", move || disabled().then_some(""))
|
||||
.into_view(cx),
|
||||
MyComponent.into_view(cx),
|
||||
h3(cx)
|
||||
.dyn_child(move || show().then(|| text("Now you see me...")))
|
||||
.into_node(cx),
|
||||
.child(cx, move || show().then(|| text("Now you see me...")))
|
||||
.into_view(cx),
|
||||
]
|
||||
}
|
||||
|
||||
struct MyComponent;
|
||||
|
||||
impl IntoNode for MyComponent {
|
||||
fn into_node(self, cx: Scope) -> Node {
|
||||
impl IntoView for MyComponent {
|
||||
fn into_view(self, cx: Scope) -> View {
|
||||
let component = Component::new("MyComponent", |cx| {
|
||||
h2(cx).child(text("MyComponent")).into_node(cx)
|
||||
h2(cx).child(cx, text("MyComponent")).into_view(cx)
|
||||
});
|
||||
|
||||
component.into_node(cx)
|
||||
component.into_view(cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use crate::events::*;
|
||||
use crate::{
|
||||
components::DynChild, ev::EventDescriptor, Element, Fragment, IntoView,
|
||||
NodeRef, Text, View,
|
||||
components::DynChild,
|
||||
ev::EventDescriptor,
|
||||
macro_helpers::{
|
||||
attribute_expression, class_expression, property_expression, Attribute,
|
||||
Child, Class, IntoAttribute, IntoChild, IntoClass, IntoProperty, Property,
|
||||
},
|
||||
Element, Fragment, IntoView, NodeRef, Text, View,
|
||||
};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use crate::{mount_child, MountKind};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_reactive::{create_effect, Scope};
|
||||
use leptos_reactive::{create_effect, create_render_effect, Scope};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
@ -208,189 +213,6 @@ impl<El: IntoElement> HtmlElement<El> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds an attribute to the element.
|
||||
#[track_caller]
|
||||
pub fn attr(
|
||||
mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
value: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
let value = value.into();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_ne!(
|
||||
name, "id",
|
||||
"to set the `id`, please use `HtmlElement::id` instead"
|
||||
);
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
if name == "class" && !value.is_empty() {
|
||||
if let Some(mut class_list) =
|
||||
self.element.get_element().get_attribute(intern("class"))
|
||||
{
|
||||
class_list.push(' ');
|
||||
class_list.push_str(&value);
|
||||
self.element.get_element().set_class_name(&class_list);
|
||||
} else {
|
||||
self.element.get_element().set_class_name(intern(&value));
|
||||
}
|
||||
} else if !value.is_empty() {
|
||||
self
|
||||
.element
|
||||
.get_element()
|
||||
.set_attribute(intern(&name), intern(&value))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if name == "class" && !value.is_empty() {
|
||||
if let Some((_, class_list)) =
|
||||
self.attrs.iter_mut().find(|(n, _)| n == "class")
|
||||
{
|
||||
let class_list = class_list.to_mut();
|
||||
|
||||
*class_list = format!("{class_list} {value}");
|
||||
} else {
|
||||
self.attrs.push((name, value))
|
||||
}
|
||||
} else if !value.is_empty() {
|
||||
self.attrs.push((name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a boolean attribute on the element, i.e., `checked`, or `disabled` in
|
||||
/// `<input type="checkbox" checked disabled />`
|
||||
#[track_caller]
|
||||
pub fn attr_bool(self, name: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.attr(name, "")
|
||||
}
|
||||
|
||||
/// Creates an attribute which will update itself when the signal changes.
|
||||
#[track_caller]
|
||||
pub fn dyn_attr<F, A>(
|
||||
mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
f: F,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn() -> Option<A> + 'static,
|
||||
A: Into<Cow<'static, str>>,
|
||||
{
|
||||
let name = name.into();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_ne!(
|
||||
name, "id",
|
||||
"to set the `id`, please use `HtmlElement::id` instead"
|
||||
);
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
create_effect(
|
||||
self.cx,
|
||||
clone!([{ *self.element.get_element() } as element], move |_| {
|
||||
if let Some(value) = f() {
|
||||
let value = value.into();
|
||||
|
||||
element.set_attribute(intern(&name), intern(&value)).unwrap();
|
||||
} else {
|
||||
element.remove_attribute(intern(&name)).unwrap();
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if let Some(value) = f() {
|
||||
let value = value.into();
|
||||
|
||||
self.attrs.push((name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a boolean attribute which changes automatically when it's
|
||||
/// signal changes.
|
||||
#[track_caller]
|
||||
pub fn dyn_attr_bool<F>(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
f: F,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn() -> bool + 'static,
|
||||
{
|
||||
self.dyn_attr(name, move || f().then_some(""))
|
||||
}
|
||||
|
||||
/// Addes the provided classes to the element. You can call this
|
||||
/// as many times as needed, seperating the classes by spaces.
|
||||
#[track_caller]
|
||||
pub fn class(self, classes: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.attr("class", classes)
|
||||
}
|
||||
|
||||
/// Addes the provided classes to the element when the predicate is true.
|
||||
/// You can call this as many times as needed, seperating the classes
|
||||
/// by spaces.
|
||||
#[track_caller]
|
||||
pub fn class_bool(
|
||||
self,
|
||||
classes: impl Into<Cow<'static, str>>,
|
||||
predicate: bool,
|
||||
) -> Self {
|
||||
if predicate {
|
||||
self.class(classes)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Addes the provided classes to the element when the signal yields
|
||||
/// true. You can call this as many times as needed, seperating the
|
||||
/// classes by spaces.
|
||||
#[track_caller]
|
||||
pub fn dyn_class<F, C>(self, f: F) -> Self
|
||||
where
|
||||
F: Fn() -> Option<C> + 'static,
|
||||
C: Into<Cow<'static, str>>,
|
||||
{
|
||||
self.dyn_attr("class", f)
|
||||
}
|
||||
|
||||
/// Adds a prop to this element.
|
||||
#[track_caller]
|
||||
pub fn prop(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
value: impl Into<JsValue>,
|
||||
) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
let name = name.into();
|
||||
let value = value.into();
|
||||
|
||||
let name: &str = &name;
|
||||
|
||||
js_sys::Reflect::set(&self, &name.into(), &value)
|
||||
.expect("set property to not err");
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
///
|
||||
/// Binds the element reference to [`NodeRef`].
|
||||
pub fn node_ref(self, node_ref: &NodeRef) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
|
@ -399,6 +221,133 @@ impl<El: IntoElement> HtmlElement<El> {
|
|||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn attr(
|
||||
mut self,
|
||||
cx: Scope,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
attr: impl IntoAttribute,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
let el = self.element.get_element();
|
||||
let value = attr.into_attribute(cx);
|
||||
match value {
|
||||
Attribute::Fn(f) => {
|
||||
let el = el.clone();
|
||||
create_render_effect(cx, move |old| {
|
||||
let new = f();
|
||||
if old.as_ref() != Some(&new) {
|
||||
attribute_expression(&el, &name, new.clone());
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
_ => attribute_expression(el, &name, value),
|
||||
};
|
||||
self
|
||||
}
|
||||
else {
|
||||
let mut attr = attr.into_attribute(cx);
|
||||
while let Attribute::Fn(f) = attr {
|
||||
attr = f();
|
||||
}
|
||||
match attr {
|
||||
Attribute::String(value) => self.attr(name, value),
|
||||
Attribute::Bool(include) => if include {
|
||||
self.attr_bool(name)
|
||||
} else {
|
||||
self
|
||||
},
|
||||
Attribute::Option(maybe) => if let Some(value) = maybe {
|
||||
self.attr(name, value)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn class(
|
||||
mut self,
|
||||
cx: Scope,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
class: impl IntoClass,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
let el = self.element.get_element();
|
||||
let class_list = el.class_list();
|
||||
let value = class.into_class(cx);
|
||||
match value {
|
||||
Class::Fn(f) => {
|
||||
create_render_effect(cx, move |old| {
|
||||
let new = f();
|
||||
if old.as_ref() != Some(&new) && (old.is_some() || new) {
|
||||
class_expression(&class_list, &name, new)
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
Class::Value(value) => class_expression(&class_list, &name, value),
|
||||
};
|
||||
self
|
||||
}
|
||||
else {
|
||||
let mut class = class.into_class(cx);
|
||||
match class {
|
||||
Class::Value(include) => self.class_bool(name, include),
|
||||
Class::Fn(f) => self.class_bool(name, f())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn prop(
|
||||
mut self,
|
||||
cx: Scope,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
value: impl IntoProperty,
|
||||
) -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
let name = name.into();
|
||||
let value = value.into_property(cx);
|
||||
let el = self.element.get_element();
|
||||
match value {
|
||||
Property::Fn(f) => {
|
||||
let el = el.clone();
|
||||
create_render_effect(cx, move |old| {
|
||||
let new = f();
|
||||
let prop_name = wasm_bindgen::intern(&name);
|
||||
if old.as_ref() != Some(&new) && !(old.is_none() && new == wasm_bindgen::JsValue::UNDEFINED) {
|
||||
property_expression(&el, &prop_name, new.clone())
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
Property::Value(value) => {
|
||||
let prop_name = wasm_bindgen::intern(&name);
|
||||
property_expression(&el, &prop_name, value)
|
||||
},
|
||||
};
|
||||
self
|
||||
}
|
||||
else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an event listener to this element.
|
||||
#[track_caller]
|
||||
pub fn on<E: EventDescriptor + 'static>(
|
||||
|
@ -425,15 +374,13 @@ impl<El: IntoElement> HtmlElement<El> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Inserts a child into this element.
|
||||
pub fn child<C: IntoView + 'static>(mut self, child: C) -> Self {
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn child(mut self, cx: Scope, child: impl IntoChild) -> Self {
|
||||
let child = child.into_child(cx);
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
{
|
||||
let child = child.into_view(self.cx);
|
||||
|
||||
mount_child(MountKind::Append(self.element.get_element()), &child)
|
||||
}
|
||||
mount_child(MountKind::Append(self.element.get_element()), &child.into_view(cx))
|
||||
}
|
||||
else {
|
||||
self.children.push(Box::new(move |cx| child.into_node(cx)));
|
||||
|
@ -442,26 +389,6 @@ impl<El: IntoElement> HtmlElement<El> {
|
|||
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a child which will automatically re-render when
|
||||
/// it's signal dependencies change.
|
||||
pub fn dyn_child<CF, N>(mut self, child_fn: CF) -> Self
|
||||
where
|
||||
CF: Fn() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
mount_child(MountKind::Append(self.element.get_element()), &DynChild::new(child_fn).into_view(self.cx))
|
||||
} else {
|
||||
self
|
||||
.children
|
||||
.push(Box::new(move |cx| DynChild::new(child_fn).into_node(cx)));
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<El: IntoElement> IntoView for HtmlElement<El> {
|
||||
|
@ -593,154 +520,6 @@ macro_rules! generate_html_tags {
|
|||
}
|
||||
}
|
||||
|
||||
// view! macro helpers
|
||||
use crate::macro_helpers::*;
|
||||
use leptos_reactive::create_render_effect;
|
||||
impl<El: IntoElement> HtmlElement<El> {
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn _attr(
|
||||
mut self,
|
||||
cx: Scope,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
attr: impl IntoAttribute,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
let el = self.element.get_element();
|
||||
let value = attr.into_attribute(cx);
|
||||
match value {
|
||||
Attribute::Fn(f) => {
|
||||
let el = el.clone();
|
||||
create_render_effect(cx, move |old| {
|
||||
let new = f();
|
||||
if old.as_ref() != Some(&new) {
|
||||
attribute_expression(&el, &name, new.clone());
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
_ => attribute_expression(el, &name, value),
|
||||
};
|
||||
self
|
||||
}
|
||||
else {
|
||||
let mut attr = attr.into_attribute(cx);
|
||||
while let Attribute::Fn(f) = attr {
|
||||
attr = f();
|
||||
}
|
||||
match attr {
|
||||
Attribute::String(value) => self.attr(name, value),
|
||||
Attribute::Bool(include) => if include {
|
||||
self.attr_bool(name)
|
||||
} else {
|
||||
self
|
||||
},
|
||||
Attribute::Option(maybe) => if let Some(value) = maybe {
|
||||
self.attr(name, value)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn _class(
|
||||
mut self,
|
||||
cx: Scope,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
class: impl IntoClass,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
let el = self.element.get_element();
|
||||
let class_list = el.class_list();
|
||||
let value = class.into_class(cx);
|
||||
match value {
|
||||
Class::Fn(f) => {
|
||||
create_render_effect(cx, move |old| {
|
||||
let new = f();
|
||||
if old.as_ref() != Some(&new) && (old.is_some() || new) {
|
||||
class_expression(&class_list, &name, new)
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
Class::Value(value) => class_expression(&class_list, &name, value),
|
||||
};
|
||||
self
|
||||
}
|
||||
else {
|
||||
let mut class = class.into_class(cx);
|
||||
match class {
|
||||
Class::Value(include) => self.class_bool(name, include),
|
||||
Class::Fn(f) => self.class_bool(name, f())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn _prop(
|
||||
mut self,
|
||||
cx: Scope,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
value: impl IntoProperty,
|
||||
) -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
let name = name.into();
|
||||
let value = value.into_property(cx);
|
||||
let el = self.element.get_element();
|
||||
match value {
|
||||
Property::Fn(f) => {
|
||||
let el = el.clone();
|
||||
create_render_effect(cx, move |old| {
|
||||
let new = f();
|
||||
let prop_name = wasm_bindgen::intern(&name);
|
||||
if old.as_ref() != Some(&new) && !(old.is_none() && new == wasm_bindgen::JsValue::UNDEFINED) {
|
||||
property_expression(&el, &prop_name, new.clone())
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
Property::Value(value) => {
|
||||
let prop_name = wasm_bindgen::intern(&name);
|
||||
property_expression(&el, &prop_name, value)
|
||||
},
|
||||
};
|
||||
self
|
||||
}
|
||||
else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn _child(mut self, cx: Scope, child: impl IntoChild) -> Self {
|
||||
let child = child.into_child(cx);
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
mount_child(MountKind::Append(self.element.get_element()), &child.into_view(cx))
|
||||
}
|
||||
else {
|
||||
self.children.push(Box::new(move |cx| child.into_node(cx)));
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
generate_html_tags![
|
||||
// ==========================
|
||||
// Main root
|
||||
|
|
|
@ -9,117 +9,123 @@ use wasm_bindgen::UnwrapThrowExt;
|
|||
/// macro’s use. You usually won't need to interact with it directly.
|
||||
#[derive(Clone)]
|
||||
pub enum Attribute {
|
||||
/// A plain string value.
|
||||
String(String),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the attribute.
|
||||
Fn(Rc<dyn Fn() -> Attribute>),
|
||||
/// An optional string value, which sets the attribute to the value if `Some` and removes the attribute if `None`.
|
||||
Option(Option<String>),
|
||||
/// A boolean attribute, which sets the attribute if `true` and removes the attribute if `false`.
|
||||
Bool(bool),
|
||||
/// A plain string value.
|
||||
String(String),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the attribute.
|
||||
Fn(Rc<dyn Fn() -> Attribute>),
|
||||
/// An optional string value, which sets the attribute to the value if `Some` and removes the attribute if `None`.
|
||||
Option(Option<String>),
|
||||
/// A boolean attribute, which sets the attribute if `true` and removes the attribute if `false`.
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
/// Converts the attribute to its HTML value at that moment so it can be rendered on the server.
|
||||
pub fn as_value_string(&self, attr_name: &'static str) -> String {
|
||||
match self {
|
||||
Attribute::String(value) => format!("{attr_name}=\"{value}\""),
|
||||
Attribute::Fn(f) => {
|
||||
let mut value = f();
|
||||
while let Attribute::Fn(f) = value {
|
||||
value = f();
|
||||
}
|
||||
value.as_value_string(attr_name)
|
||||
}
|
||||
Attribute::Option(value) => value
|
||||
.as_ref()
|
||||
.map(|value| format!("{attr_name}=\"{value}\""))
|
||||
.unwrap_or_default(),
|
||||
Attribute::Bool(include) => {
|
||||
if *include {
|
||||
attr_name.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
/// Converts the attribute to its HTML value at that moment so it can be rendered on the server.
|
||||
pub fn as_value_string(&self, attr_name: &'static str) -> String {
|
||||
match self {
|
||||
Attribute::String(value) => format!("{attr_name}=\"{value}\""),
|
||||
Attribute::Fn(f) => {
|
||||
let mut value = f();
|
||||
while let Attribute::Fn(f) = value {
|
||||
value = f();
|
||||
}
|
||||
value.as_value_string(attr_name)
|
||||
}
|
||||
Attribute::Option(value) => value
|
||||
.as_ref()
|
||||
.map(|value| format!("{attr_name}=\"{value}\""))
|
||||
.unwrap_or_default(),
|
||||
Attribute::Bool(include) => {
|
||||
if *include {
|
||||
attr_name.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Attribute {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::String(l0), Self::String(r0)) => l0 == r0,
|
||||
(Self::Fn(_), Self::Fn(_)) => false,
|
||||
(Self::Option(l0), Self::Option(r0)) => l0 == r0,
|
||||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::String(l0), Self::String(r0)) => l0 == r0,
|
||||
(Self::Fn(_), Self::Fn(_)) => false,
|
||||
(Self::Option(l0), Self::Option(r0)) => l0 == r0,
|
||||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Attribute {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::String(arg0) => f.debug_tuple("String").field(arg0).finish(),
|
||||
Self::Fn(_) => f.debug_tuple("Fn").finish(),
|
||||
Self::Option(arg0) => f.debug_tuple("Option").field(arg0).finish(),
|
||||
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::String(arg0) => f.debug_tuple("String").field(arg0).finish(),
|
||||
Self::Fn(_) => f.debug_tuple("Fn").finish(),
|
||||
Self::Option(arg0) => f.debug_tuple("Option").field(arg0).finish(),
|
||||
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts some type into an [Attribute].
|
||||
///
|
||||
/// This is implemented by default for Rust primitive and string types.
|
||||
pub trait IntoAttribute {
|
||||
/// Converts the object into an [Attribute].
|
||||
fn into_attribute(self, cx: Scope) -> Attribute;
|
||||
/// Converts the object into an [Attribute].
|
||||
fn into_attribute(self, cx: Scope) -> Attribute;
|
||||
}
|
||||
|
||||
impl IntoAttribute for String {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::String(self)
|
||||
}
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::String(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for bool {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::Bool(self)
|
||||
}
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::Bool(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<String> {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::Option(self)
|
||||
}
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::Option(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> IntoAttribute for T
|
||||
where
|
||||
T: Fn() -> U + 'static,
|
||||
U: IntoAttribute,
|
||||
T: Fn() -> U + 'static,
|
||||
U: IntoAttribute,
|
||||
{
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
let modified_fn = Rc::new(move || (self)().into_attribute(cx));
|
||||
Attribute::Fn(modified_fn)
|
||||
}
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
let modified_fn = Rc::new(move || (self)().into_attribute(cx));
|
||||
Attribute::Fn(modified_fn)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoAttribute> IntoAttribute for (Scope, T) {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
self.1.into_attribute(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! attr_type {
|
||||
($attr_type:ty) => {
|
||||
impl IntoAttribute for $attr_type {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::String(self.to_string())
|
||||
}
|
||||
}
|
||||
($attr_type:ty) => {
|
||||
impl IntoAttribute for $attr_type {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<$attr_type> {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::Option(self.map(|n| n.to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
impl IntoAttribute for Option<$attr_type> {
|
||||
fn into_attribute(self, _cx: Scope) -> Attribute {
|
||||
Attribute::Option(self.map(|n| n.to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
attr_type!(&String);
|
||||
|
@ -140,39 +146,43 @@ attr_type!(f32);
|
|||
attr_type!(f64);
|
||||
attr_type!(char);
|
||||
|
||||
pub fn attribute_expression(el: &web_sys::Element, attr_name: &str, value: Attribute) {
|
||||
match value {
|
||||
Attribute::String(value) => {
|
||||
let value = wasm_bindgen::intern(&value);
|
||||
if attr_name == "inner_html" {
|
||||
el.set_inner_html(&value);
|
||||
} else {
|
||||
let attr_name = wasm_bindgen::intern(attr_name);
|
||||
el.set_attribute(attr_name, &value).unwrap_throw();
|
||||
}
|
||||
}
|
||||
Attribute::Option(value) => {
|
||||
if attr_name == "inner_html" {
|
||||
el.set_inner_html(&value.unwrap_or_default());
|
||||
} else {
|
||||
let attr_name = wasm_bindgen::intern(attr_name);
|
||||
match value {
|
||||
Some(value) => {
|
||||
let value = wasm_bindgen::intern(&value);
|
||||
el.set_attribute(attr_name, &value).unwrap_throw();
|
||||
}
|
||||
None => el.remove_attribute(attr_name).unwrap_throw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Attribute::Bool(value) => {
|
||||
let attr_name = wasm_bindgen::intern(attr_name);
|
||||
if value {
|
||||
el.set_attribute(attr_name, attr_name).unwrap_throw();
|
||||
} else {
|
||||
el.remove_attribute(attr_name).unwrap_throw();
|
||||
}
|
||||
}
|
||||
_ => panic!("Remove nested Fn in Attribute"),
|
||||
pub fn attribute_expression(
|
||||
el: &web_sys::Element,
|
||||
attr_name: &str,
|
||||
value: Attribute,
|
||||
) {
|
||||
match value {
|
||||
Attribute::String(value) => {
|
||||
let value = wasm_bindgen::intern(&value);
|
||||
if attr_name == "inner_html" {
|
||||
el.set_inner_html(&value);
|
||||
} else {
|
||||
let attr_name = wasm_bindgen::intern(attr_name);
|
||||
el.set_attribute(attr_name, &value).unwrap_throw();
|
||||
}
|
||||
}
|
||||
}
|
||||
Attribute::Option(value) => {
|
||||
if attr_name == "inner_html" {
|
||||
el.set_inner_html(&value.unwrap_or_default());
|
||||
} else {
|
||||
let attr_name = wasm_bindgen::intern(attr_name);
|
||||
match value {
|
||||
Some(value) => {
|
||||
let value = wasm_bindgen::intern(&value);
|
||||
el.set_attribute(attr_name, &value).unwrap_throw();
|
||||
}
|
||||
None => el.remove_attribute(attr_name).unwrap_throw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Attribute::Bool(value) => {
|
||||
let attr_name = wasm_bindgen::intern(attr_name);
|
||||
if value {
|
||||
el.set_attribute(attr_name, attr_name).unwrap_throw();
|
||||
} else {
|
||||
el.remove_attribute(attr_name).unwrap_throw();
|
||||
}
|
||||
}
|
||||
_ => panic!("Remove nested Fn in Attribute"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: IntoChild> IntoChild for (Scope, T) {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
self.1.into_child(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! node_type {
|
||||
($child_type:ty) => {
|
||||
impl IntoChild for $child_type {
|
||||
|
|
|
@ -8,62 +8,72 @@ use wasm_bindgen::UnwrapThrowExt;
|
|||
/// This mostly exists for the [`view`](https://docs.rs/leptos_macro/latest/leptos_macro/macro.view.html)
|
||||
/// macro’s use. You usually won't need to interact with it directly.
|
||||
pub enum Class {
|
||||
/// Whether the class is present.
|
||||
Value(bool),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to toggle the class.
|
||||
Fn(Box<dyn Fn() -> bool>),
|
||||
/// Whether the class is present.
|
||||
Value(bool),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to toggle the class.
|
||||
Fn(Box<dyn Fn() -> bool>),
|
||||
}
|
||||
|
||||
/// Converts some type into a [Class].
|
||||
pub trait IntoClass {
|
||||
/// Converts the object into a [Class].
|
||||
fn into_class(self, cx: Scope) -> Class;
|
||||
/// Converts the object into a [Class].
|
||||
fn into_class(self, cx: Scope) -> Class;
|
||||
}
|
||||
|
||||
impl IntoClass for bool {
|
||||
fn into_class(self, _cx: Scope) -> Class {
|
||||
Class::Value(self)
|
||||
}
|
||||
fn into_class(self, _cx: Scope) -> Class {
|
||||
Class::Value(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoClass for T
|
||||
where
|
||||
T: Fn() -> bool + 'static,
|
||||
T: Fn() -> bool + 'static,
|
||||
{
|
||||
fn into_class(self, _cx: Scope) -> Class {
|
||||
let modified_fn = Box::new(self);
|
||||
Class::Fn(modified_fn)
|
||||
}
|
||||
fn into_class(self, _cx: Scope) -> Class {
|
||||
let modified_fn = Box::new(self);
|
||||
Class::Fn(modified_fn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Class {
|
||||
/// Converts the class to its HTML value at that moment so it can be rendered on the server.
|
||||
pub fn as_value_string(&self, class_name: &'static str) -> &'static str {
|
||||
match self {
|
||||
Class::Value(value) => {
|
||||
if *value {
|
||||
class_name
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
Class::Fn(f) => {
|
||||
let value = f();
|
||||
if value {
|
||||
class_name
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
/// Converts the class to its HTML value at that moment so it can be rendered on the server.
|
||||
pub fn as_value_string(&self, class_name: &'static str) -> &'static str {
|
||||
match self {
|
||||
Class::Value(value) => {
|
||||
if *value {
|
||||
class_name
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
Class::Fn(f) => {
|
||||
let value = f();
|
||||
if value {
|
||||
class_name
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn class_expression(class_list: &web_sys::DomTokenList, class_name: &str, value: bool) {
|
||||
let class_name = wasm_bindgen::intern(class_name);
|
||||
if value {
|
||||
class_list.add_1(class_name).unwrap_throw();
|
||||
} else {
|
||||
class_list.remove_1(class_name).unwrap_throw();
|
||||
}
|
||||
}
|
||||
impl<T: IntoClass> IntoClass for (Scope, T) {
|
||||
fn into_class(self, cx: Scope) -> Class {
|
||||
self.1.into_class(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn class_expression(
|
||||
class_list: &web_sys::DomTokenList,
|
||||
class_name: &str,
|
||||
value: bool,
|
||||
) {
|
||||
let class_name = wasm_bindgen::intern(class_name);
|
||||
if value {
|
||||
class_list.add_1(class_name).unwrap_throw();
|
||||
} else {
|
||||
class_list.remove_1(class_name).unwrap_throw();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,45 +7,51 @@ use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
|||
/// This mostly exists for the [`view`](https://docs.rs/leptos_macro/latest/leptos_macro/macro.view.html)
|
||||
/// macro’s use. You usually won't need to interact with it directly.
|
||||
pub enum Property {
|
||||
/// A static JavaScript value.
|
||||
Value(JsValue),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to toggle the class.
|
||||
Fn(Box<dyn Fn() -> JsValue>),
|
||||
/// A static JavaScript value.
|
||||
Value(JsValue),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to toggle the class.
|
||||
Fn(Box<dyn Fn() -> JsValue>),
|
||||
}
|
||||
|
||||
/// Converts some type into a [Property].
|
||||
///
|
||||
/// This is implemented by default for Rust primitive types, [String] and friends, and [JsValue].
|
||||
pub trait IntoProperty {
|
||||
/// Converts the object into a [Property].
|
||||
fn into_property(self, cx: Scope) -> Property;
|
||||
/// Converts the object into a [Property].
|
||||
fn into_property(self, cx: Scope) -> Property;
|
||||
}
|
||||
|
||||
impl<T, U> IntoProperty for T
|
||||
where
|
||||
T: Fn() -> U + 'static,
|
||||
U: Into<JsValue>,
|
||||
T: Fn() -> U + 'static,
|
||||
U: Into<JsValue>,
|
||||
{
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
let modified_fn = Box::new(move || self().into());
|
||||
Property::Fn(modified_fn)
|
||||
}
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
let modified_fn = Box::new(move || self().into());
|
||||
Property::Fn(modified_fn)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoProperty> IntoProperty for (Scope, T) {
|
||||
fn into_property(self, cx: Scope) -> Property {
|
||||
self.1.into_property(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! prop_type {
|
||||
($prop_type:ty) => {
|
||||
impl IntoProperty for $prop_type {
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
}
|
||||
($prop_type:ty) => {
|
||||
impl IntoProperty for $prop_type {
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoProperty for Option<$prop_type> {
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
impl IntoProperty for Option<$prop_type> {
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
prop_type!(JsValue);
|
||||
|
@ -68,6 +74,11 @@ prop_type!(f32);
|
|||
prop_type!(f64);
|
||||
prop_type!(bool);
|
||||
|
||||
pub fn property_expression(el: &web_sys::Element, prop_name: &str, value: JsValue) {
|
||||
js_sys::Reflect::set(el, &JsValue::from_str(prop_name), &value).unwrap_throw();
|
||||
}
|
||||
pub fn property_expression(
|
||||
el: &web_sys::Element,
|
||||
prop_name: &str,
|
||||
value: JsValue,
|
||||
) {
|
||||
js_sys::Reflect::set(el, &JsValue::from_str(prop_name), &value)
|
||||
.unwrap_throw();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue