impl all the macro helper traits for (Scope, T)

This commit is contained in:
Jose Quesada 2022-12-05 14:09:39 -06:00
parent 13f8006162
commit 59f753cebb
6 changed files with 379 additions and 571 deletions

View File

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

View File

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

View File

@ -9,117 +9,123 @@ use wasm_bindgen::UnwrapThrowExt;
/// macros 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"),
}
}

View File

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

View File

@ -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)
/// macros 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();
}
}

View File

@ -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)
/// macros 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();
}