Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: directives #1821

Merged
merged 31 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2a04420
started on directives
maccesch Oct 1, 2023
415afa9
fixes for directive params
maccesch Oct 1, 2023
f06fb44
fix: missing `Copy` impl on `Callback`
gbj Sep 30, 2023
6ef2279
docs: missing module-level docs for `Callback`
gbj Sep 30, 2023
d27f977
fix: `template!` cfg condition (#1822)
Dragonink Oct 2, 2023
f5beeef
docs: DX improvements: add section about jetbrains `intellij-rust` (#…
sebadob Oct 2, 2023
b87c74a
feat: optional fallbacks for `Show`, `Suspense`, and `Transition` (#1…
maccesch Oct 2, 2023
5570cd1
fix: don't overwrite `<Html/>` props (closes #1828)
dgsantana Oct 2, 2023
3ee1a7c
fix: correctly handle `Suspense` with local resources during hydratio…
gbj Oct 2, 2023
988d2b2
fix: correctly quote spread attributes in `{..attrs}` syntax (closes …
gbj Oct 2, 2023
8c28b15
feat: allow disposing of Signal & StoredValue (#1849)
PaulWagener Oct 6, 2023
b128c08
fix: make Async Mode return Content-Type header in Response (#1851)
benwis Oct 6, 2023
dc61d2a
feat: support stored values in `with!` and `update!` (#1836)
blorbb Oct 6, 2023
9de8fe0
chore: removed warning in build artefacts. (#1840)
martinfrances107 Oct 6, 2023
8b9fdd6
fix: update log debug to use get_untracked for logged in user to reso…
kevinold Oct 6, 2023
d118496
fix: panic during `generate_route_list` if you immediately dispatch a…
gbj Oct 6, 2023
464dc9f
fix: `clippy` "needless lifetimes" warning (closes #1825) (#1852)
gbj Oct 6, 2023
f8311e8
docs: fix hidden #two
he00741098 Oct 6, 2023
c9b588c
`v0.5.1`
gbj Oct 6, 2023
17a2d53
docs: update CodeSandboxes to 0.5
gbj Oct 6, 2023
0c28679
add `awesome-leptos` to README
gbj Oct 7, 2023
e5cd443
docs: clarify what "once per signal change" means (#1858)
arcstur Oct 7, 2023
49fc6be
fix: documentation in `leptos_reactive::Trigger` (#1844)
hiraginoyuki Oct 7, 2023
ce3066e
made directives use NodeRef
maccesch Oct 7, 2023
f64bb26
added directive example
maccesch Oct 7, 2023
16ef88d
fixed directive on components
maccesch Oct 19, 2023
2f83430
added test for directives
maccesch Oct 19, 2023
b27a7df
Merge remote-tracking branch 'upstream/main' into directive
maccesch Oct 19, 2023
95d08ab
fixed docs typo
maccesch Oct 19, 2023
d40818d
chore: make clippy happy
maccesch Oct 19, 2023
cd9ae5b
chore: make clippy happy
maccesch Oct 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/directives/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
rustflags = ["--cfg=web_sys_unstable_apis"]
17 changes: 17 additions & 0 deletions examples/directives/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "directives"
version = "0.1.0"
edition = "2021"

[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"
web-sys = { version = "0.3", features = ["Clipboard", "Navigator"] }

[dev-dependencies]
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = "0.3"
gloo-timers = { version = "0.3", features = ["futures"] }
5 changes: 5 additions & 0 deletions examples/directives/Makefile.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/wasm-test.toml" },
{ path = "../cargo-make/trunk_server.toml" },
]
7 changes: 7 additions & 0 deletions examples/directives/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Leptos Directives Example

This example showcases a basic leptos app that shows how to write and use directives.

## Getting Started

See the [Examples README](../README.md) for setup and run instructions.
7 changes: 7 additions & 0 deletions examples/directives/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z" data-weak-refs/>
</head>
<body></body>
</html>
3 changes: 3 additions & 0 deletions examples/directives/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

[toolchain]
channel = "nightly"
51 changes: 51 additions & 0 deletions examples/directives/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use leptos::{ev::click, html::AnyElement, *};

pub fn highlight(el: HtmlElement<AnyElement>) {
let mut highlighted = false;

let _ = el.clone().on(click, move |_| {
highlighted = !highlighted;

if highlighted {
let _ = el.clone().style("background-color", "yellow");
} else {
let _ = el.clone().style("background-color", "transparent");
}
});
}

pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
let content = content.to_string();

let _ = el.clone().on(click, move |evt| {
evt.prevent_default();
evt.stop_propagation();

let _ = window()
.navigator()
.clipboard()
.expect("navigator.clipboard to be available")
.write_text(&content);

let _ = el.clone().inner_html(format!("Copied \"{}\"", &content));
});
}

#[component]
pub fn SomeComponent() -> impl IntoView {
view! {
<p>Some paragraphs</p>
<p>that can be clicked</p>
<p>in order to highlight them</p>
}
}

#[component]
pub fn App() -> impl IntoView {
let data = "Hello World!";

view! {
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
<SomeComponent use:highlight />
}
}
8 changes: 8 additions & 0 deletions examples/directives/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use directives::App;
use leptos::*;

fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|| view! { <App/> })
}
58 changes: 58 additions & 0 deletions examples/directives/tests/web.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use gloo_timers::future::sleep;
use std::time::Duration;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

wasm_bindgen_test_configure!(run_in_browser);
use directives::App;
use leptos::*;
use web_sys::HtmlElement;

#[wasm_bindgen_test]
async fn test_directives() {
mount_to_body(|| view! { <App/> });
sleep(Duration::ZERO).await;

let document = leptos::document();
let paragraphs = document.query_selector_all("p").unwrap();

assert_eq!(paragraphs.length(), 3);

for i in 0..paragraphs.length() {
println!("i: {}", i);
let p = paragraphs
.item(i)
.unwrap()
.dyn_into::<HtmlElement>()
.unwrap();
assert_eq!(
p.style().get_property_value("background-color").unwrap(),
""
);

p.click();

assert_eq!(
p.style().get_property_value("background-color").unwrap(),
"yellow"
);

p.click();

assert_eq!(
p.style().get_property_value("background-color").unwrap(),
"transparent"
);
}

let a = document
.query_selector("a")
.unwrap()
.unwrap()
.dyn_into::<HtmlElement>()
.unwrap();
assert_eq!(a.inner_html(), "Copy \"Hello World!\" to clipboard");

a.click();
assert_eq!(a.inner_html(), "Copied \"Hello World!\"");
}
83 changes: 83 additions & 0 deletions leptos_dom/src/directive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::{html::AnyElement, HtmlElement};
use std::rc::Rc;

/// 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: HtmlElement<AnyElement>) {
/// // do sth
/// }
///
/// // This requires an attribute value
/// fn another_directive(el: HtmlElement<AnyElement>, 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 Directive<T: ?Sized, P> {
/// Calls the handler function
fn run(&self, el: HtmlElement<AnyElement>, param: P);
}

impl<F> Directive<(HtmlElement<AnyElement>,), ()> for F
where
F: Fn(HtmlElement<AnyElement>),
{
fn run(&self, el: HtmlElement<AnyElement>, _: ()) {
self(el)
}
}

impl<F, P> Directive<(HtmlElement<AnyElement>, P), P> for F
where
F: Fn(HtmlElement<AnyElement>, P),
{
fn run(&self, el: HtmlElement<AnyElement>, param: P) {
self(el, param);
}
}

impl<T: ?Sized, P> Directive<T, P> for Rc<dyn Directive<T, P>> {
fn run(&self, el: HtmlElement<AnyElement>, param: P) {
(**self).run(el, param)
}
}

impl<T: ?Sized, P> Directive<T, P> for Box<dyn Directive<T, P>> {
fn run(&self, el: HtmlElement<AnyElement>, param: P) {
(**self).run(el, param);
}
}
30 changes: 26 additions & 4 deletions leptos_dom/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,16 @@ cfg_if! {
}

use crate::{
create_node_ref,
ev::EventDescriptor,
hydration::HydrationCtx,
macro_helpers::{
Attribute, IntoAttribute, IntoClass, IntoProperty, IntoStyle,
},
Element, Fragment, IntoView, NodeRef, Text, View,
Directive, Element, Fragment, IntoView, NodeRef, Text, View,
};
use leptos_reactive::Oco;
use std::fmt;
use leptos_reactive::{create_effect, Oco};
use std::{fmt, rc::Rc};

/// Trait which allows creating an element tag.
pub trait ElementDescriptor: ElementDescriptorBounds {
Expand Down Expand Up @@ -508,7 +509,6 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
use once_cell::unsync::OnceCell;
use std::{
cell::RefCell,
rc::Rc,
task::{Poll, Waker},
};

Expand Down Expand Up @@ -1146,6 +1146,28 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
}
}

impl<El: ElementDescriptor + Clone + 'static> HtmlElement<El> {
/// Bind the directive to the element.
#[inline(always)]
pub fn directive<T: ?Sized, P: Clone + 'static>(
self,
handler: impl Directive<T, P> + 'static,
param: P,
) -> Self {
let node_ref = create_node_ref::<El>();

let handler = Rc::new(handler);

let _ = create_effect(move |_| {
if let Some(el) = node_ref.get() {
Rc::clone(&handler).run(el.into_any(), param.clone());
}
});

self.node_ref(node_ref)
}
}

impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
#[cfg_attr(any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "<HtmlElement />", skip_all, fields(tag = %self.element.name())))]
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), inline(always))]
Expand Down
Loading
Loading