mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
preliminary work on directives (not useful yet until we have an ElementExt that allows you to do things declaratively from an Element
This commit is contained in:
parent
fe7c7c3a99
commit
3ebea79e05
5 changed files with 340 additions and 7 deletions
|
@ -1,7 +1,10 @@
|
|||
use leptos::{ev::click, html::AnyElement, prelude::*};
|
||||
use leptos::{ev::click, html::a, prelude::*};
|
||||
use web_sys::{Element, HtmlElement};
|
||||
|
||||
// no extra parameter
|
||||
pub fn highlight(el: HtmlElement<AnyElement>) {
|
||||
pub fn highlight(el: Element, param: ()) {
|
||||
// TODO
|
||||
/*
|
||||
let mut highlighted = false;
|
||||
|
||||
let _ = el.clone().on(click, move |_| {
|
||||
|
@ -13,13 +16,16 @@ pub fn highlight(el: HtmlElement<AnyElement>) {
|
|||
let _ = el.clone().style("background-color", "transparent");
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
// one extra parameter
|
||||
pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
|
||||
pub fn copy_to_clipboard(el: Element, content: &'static str) {
|
||||
// TODO
|
||||
let content = content.to_string();
|
||||
leptos::logging::log!("running copy_to_clipboard directive");
|
||||
|
||||
let _ = el.clone().on(click, move |evt| {
|
||||
/*let _ = el.clone().on(click, move |evt| {
|
||||
evt.prevent_default();
|
||||
evt.stop_propagation();
|
||||
|
||||
|
@ -30,7 +36,7 @@ pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
|
|||
.write_text(&content);
|
||||
|
||||
let _ = el.clone().inner_html(format!("Copied \"{}\"", &content));
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
// custom parameter
|
||||
|
@ -52,7 +58,9 @@ impl From<()> for Amount {
|
|||
}
|
||||
|
||||
// .into() will automatically be called on the parameter
|
||||
pub fn add_dot(el: HtmlElement<AnyElement>, amount: Amount) {
|
||||
pub fn add_dot(el: Element, amount: Amount) {
|
||||
// TODO
|
||||
/*
|
||||
_ = el.clone().on(click, move |_| {
|
||||
el.set_inner_text(&format!(
|
||||
"{}{}",
|
||||
|
@ -60,6 +68,7 @@ pub fn add_dot(el: HtmlElement<AnyElement>, amount: Amount) {
|
|||
".".repeat(amount.0)
|
||||
))
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
@ -78,7 +87,7 @@ pub fn App() -> impl IntoView {
|
|||
view! {
|
||||
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
|
||||
// automatically applies the directive to every root element in `SomeComponent`
|
||||
<SomeComponent use:highlight />
|
||||
//<SomeComponent use:highlight />
|
||||
// no value will default to `().into()`
|
||||
<button use:add_dot>"Add a dot"</button>
|
||||
// `5.into()` automatically called
|
||||
|
|
|
@ -409,6 +409,8 @@ fn attribute_to_tokens(
|
|||
quote! {
|
||||
.#node_ref(#value)
|
||||
}
|
||||
} else if let Some(name) = name.strip_prefix("use:") {
|
||||
directive_call_from_attribute_node(node, name)
|
||||
} else if let Some(name) = name.strip_prefix("on:") {
|
||||
event_to_tokens(name, node)
|
||||
} else if let Some(name) = name.strip_prefix("class:") {
|
||||
|
@ -489,6 +491,16 @@ pub(crate) fn attribute_absolute(
|
|||
quote! { ::leptos::tachys::html::attribute::#key(#value) },
|
||||
)
|
||||
}
|
||||
} else if id == "use" {
|
||||
let key = &parts[1];
|
||||
let param = if let Some(value) = node.value() {
|
||||
quote!(::std::convert::Into::into(#value))
|
||||
} else {
|
||||
quote_spanned!(node.key.span()=> ().into())
|
||||
};
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::directive::directive((#key, #param)) },
|
||||
)
|
||||
} else if id == "style" || id == "class" {
|
||||
let key = &node.key.to_string();
|
||||
let key = key
|
||||
|
@ -1020,3 +1032,18 @@ pub(crate) fn ident_from_tag_name(tag_name: &NodeName) -> Ident {
|
|||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn directive_call_from_attribute_node(
|
||||
attr: &KeyedAttribute,
|
||||
directive_name: &str,
|
||||
) -> TokenStream {
|
||||
let handler = syn::Ident::new(directive_name, attr.key.span());
|
||||
|
||||
let param = if let Some(value) = attr.value() {
|
||||
quote!(::std::convert::Into::into(#value))
|
||||
} else {
|
||||
quote_spanned!(attr.key.span()=> ().into())
|
||||
};
|
||||
|
||||
quote! { .directive(#handler, #[allow(clippy::useless_conversion)] #param) }
|
||||
}
|
||||
|
|
295
tachys/src/html/directive.rs
Normal file
295
tachys/src/html/directive.rs
Normal file
|
@ -0,0 +1,295 @@
|
|||
use super::attribute::{Attribute, NextAttribute};
|
||||
use crate::{
|
||||
prelude::AddAnyAttr,
|
||||
renderer::Renderer,
|
||||
view::{Position, ToTemplate},
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
pub trait DirectiveAttribute<T, P, D, Rndr>
|
||||
where
|
||||
D: IntoDirective<T, P, Rndr>,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output;
|
||||
|
||||
fn directive(self, handler: D, param: P) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<V, T, P, D, Rndr> DirectiveAttribute<T, P, D, Rndr> for V
|
||||
where
|
||||
V: AddAnyAttr<Rndr>,
|
||||
D: IntoDirective<T, P, Rndr>,
|
||||
P: Clone + 'static,
|
||||
T: 'static,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr<Rndr>>::Output<Directive<T, D, P, Rndr>>;
|
||||
|
||||
fn directive(self, handler: D, param: P) -> Self::Output {
|
||||
self.add_any_attr(directive(handler, param))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn directive<T, P, D, R>(handler: D, param: P) -> Directive<T, D, P, R>
|
||||
where
|
||||
D: IntoDirective<T, P, R>,
|
||||
R: Renderer,
|
||||
{
|
||||
Directive(SendWrapper::new(DirectiveInner {
|
||||
handler,
|
||||
param,
|
||||
t: PhantomData,
|
||||
rndr: PhantomData,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Directive<T, D, P, R>(SendWrapper<DirectiveInner<T, D, P, R>>);
|
||||
|
||||
impl<T, D, P, R> Clone for Directive<T, D, P, R>
|
||||
where
|
||||
P: Clone + 'static,
|
||||
D: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectiveInner<T, D, P, R> {
|
||||
handler: D,
|
||||
param: P,
|
||||
t: PhantomData<T>,
|
||||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<T, D, P, R> Clone for DirectiveInner<T, D, P, R>
|
||||
where
|
||||
P: Clone + 'static,
|
||||
D: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
handler: self.handler.clone(),
|
||||
param: self.param.clone(),
|
||||
t: PhantomData,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, D, R> Attribute<R> for Directive<T, D, P, R>
|
||||
where
|
||||
D: IntoDirective<T, P, R>,
|
||||
P: Clone + 'static, // TODO this is just here to make them cloneable
|
||||
T: 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
type State = R::Element;
|
||||
type Cloneable = Directive<T, D::Cloneable, P, R>;
|
||||
type CloneableOwned = Directive<T, D::Cloneable, P, R>;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn to_html(
|
||||
self,
|
||||
_buf: &mut String,
|
||||
_class: &mut String,
|
||||
_style: &mut String,
|
||||
_inner_html: &mut String,
|
||||
) {
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
|
||||
let inner = self.0.take();
|
||||
inner.handler.run(el.clone(), inner.param);
|
||||
el.clone()
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element) -> Self::State {
|
||||
let inner = self.0.take();
|
||||
inner.handler.run(el.clone(), inner.param);
|
||||
el.clone()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let inner = self.0.take();
|
||||
inner.handler.run(state.clone(), inner.param);
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self.into_cloneable_owned()
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
let DirectiveInner {
|
||||
handler,
|
||||
param,
|
||||
t,
|
||||
rndr,
|
||||
} = self.0.take();
|
||||
Directive(SendWrapper::new(DirectiveInner {
|
||||
handler: handler.into_cloneable(),
|
||||
param,
|
||||
t,
|
||||
rndr,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D, P, R> NextAttribute<R> for Directive<T, D, P, R>
|
||||
where
|
||||
D: IntoDirective<T, P, R>,
|
||||
P: Clone + 'static,
|
||||
T: 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
type Output<NewAttr: Attribute<R>> = (Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
(self, new_attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D, P, R> ToTemplate for Directive<T, D, P, R> {
|
||||
const CLASS: &'static str = "";
|
||||
|
||||
fn to_template(
|
||||
_buf: &mut String,
|
||||
_class: &mut String,
|
||||
_style: &mut String,
|
||||
_inner_html: &mut String,
|
||||
_position: &mut Position,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for a directive handler function.
|
||||
/// This is used so it's possible to use functions with one or two
|
||||
/// parameters as directive handlers.
|
||||
///
|
||||
/// You can use directives like the following.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::{*, html::AnyElement};
|
||||
///
|
||||
/// // This doesn't take an attribute value
|
||||
/// fn my_directive(el: R::Element) {
|
||||
/// // do sth
|
||||
/// }
|
||||
///
|
||||
/// // This requires an attribute value
|
||||
/// fn another_directive(el: R::Element, params: i32) {
|
||||
/// // do sth
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyComponent() -> impl IntoView {
|
||||
/// view! {
|
||||
/// // no attribute value
|
||||
/// <div use:my_directive></div>
|
||||
///
|
||||
/// // with an attribute value
|
||||
/// <div use:another_directive=8></div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// A directive is just syntactic sugar for
|
||||
///
|
||||
/// ```ignore
|
||||
/// let node_ref = create_node_ref();
|
||||
///
|
||||
/// create_effect(move |_| {
|
||||
/// if let Some(el) = node_ref.get() {
|
||||
/// directive_func(el, possibly_some_param);
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// A directive can be a function with one or two parameters.
|
||||
/// The first is the element the directive is added to and the optional
|
||||
/// second is the parameter that is provided in the attribute.
|
||||
pub trait IntoDirective<T: ?Sized, P, R: Renderer> {
|
||||
type Cloneable: IntoDirective<T, P, R> + Clone + 'static;
|
||||
|
||||
/// Calls the handler function
|
||||
fn run(&self, el: R::Element, param: P);
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable;
|
||||
}
|
||||
|
||||
impl<F, R> IntoDirective<(R::Element,), (), R> for F
|
||||
where
|
||||
F: Fn(R::Element) + 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
type Cloneable = Arc<dyn Fn(R::Element)>;
|
||||
|
||||
fn run(&self, el: R::Element, _: ()) {
|
||||
self(el)
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> IntoDirective<(R::Element,), (), R> for Arc<dyn Fn(R::Element)>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type Cloneable = Arc<dyn Fn(R::Element)>;
|
||||
|
||||
fn run(&self, el: R::Element, _: ()) {
|
||||
self(el)
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, P, R> IntoDirective<(R::Element, P), P, R> for F
|
||||
where
|
||||
F: Fn(R::Element, P) + 'static,
|
||||
P: 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
type Cloneable = Arc<dyn Fn(R::Element, P)>;
|
||||
|
||||
fn run(&self, el: R::Element, param: P) {
|
||||
self(el, param);
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, R> IntoDirective<(R::Element, P), P, R> for Arc<dyn Fn(R::Element, P)>
|
||||
where
|
||||
R: Renderer,
|
||||
P: 'static,
|
||||
{
|
||||
type Cloneable = Arc<dyn Fn(R::Element, P)>;
|
||||
|
||||
fn run(&self, el: R::Element, param: P) {
|
||||
self(el, param)
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ use std::marker::PhantomData;
|
|||
|
||||
pub mod attribute;
|
||||
pub mod class;
|
||||
pub mod directive;
|
||||
pub mod element;
|
||||
pub mod event;
|
||||
pub mod islands;
|
||||
|
|
|
@ -14,6 +14,7 @@ pub mod prelude {
|
|||
OnTargetAttribute, PropAttribute, StyleAttribute,
|
||||
},
|
||||
},
|
||||
directive::DirectiveAttribute,
|
||||
element::{ElementChild, InnerHtmlAttribute},
|
||||
node_ref::NodeRefAttribute,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue