Merge branch 'upstream' into router-typesafe
This commit is contained in:
commit
92755a381d
21 files changed
+297
-94
No files matched your search
+1
-1
@@ -72,7 +72,7 @@ tokio = { version = "1.16.1", features = ["full"] }
|
|||||||
reqwest = { version = "0.11.9", features = ["json"] }
|
reqwest = { version = "0.11.9", features = ["json"] }
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.10.0"
|
||||||
simple_logger = "4.0.0"
|
simple_logger = "4.0.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#![allow(unused)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
// ANCHOR: all
|
||||||
|
|
||||||
|
// ANCHOR: main
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use axum::{response::Html, routing::get, Router};
|
||||||
|
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
println!("listening on http://{}", addr);
|
||||||
|
|
||||||
|
axum::Server::bind(&addr)
|
||||||
|
.serve(
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(app_endpoint))
|
||||||
|
.into_make_service(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR_END: main
|
||||||
|
|
||||||
|
// ANCHOR: endpoint
|
||||||
|
async fn app_endpoint() -> Html<String> {
|
||||||
|
// render the rsx! macro to HTML
|
||||||
|
Html(dioxus_ssr::render_lazy(rsx! {
|
||||||
|
div { "hello world!" }
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
// ANCHOR_END: endpoint
|
||||||
|
|
||||||
|
// ANCHOR: second_endpoint
|
||||||
|
async fn second_app_endpoint() -> Html<String> {
|
||||||
|
// create a component that renders a div with the text "hello world"
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx!(div { "hello world" }))
|
||||||
|
}
|
||||||
|
// create a VirtualDom with the app component
|
||||||
|
let mut app = VirtualDom::new(app);
|
||||||
|
// rebuild the VirtualDom before rendering
|
||||||
|
let _ = app.rebuild();
|
||||||
|
|
||||||
|
// render the VirtualDom to HTML
|
||||||
|
Html(dioxus_ssr::render(&app))
|
||||||
|
}
|
||||||
|
// ANCHOR_END: second_endpoint
|
||||||
|
|
||||||
|
// ANCHOR: component
|
||||||
|
// define a component that renders a div with the text "Hello, world!"
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// ANCHOR_END: component
|
||||||
|
// ANCHOR_END: all
|
||||||
@@ -40,54 +40,30 @@ tokio = { version = "1.15.0", features = ["full"] }
|
|||||||
Now, set up your Axum app to respond on an endpoint.
|
Now, set up your Axum app to respond on an endpoint.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use axum::{response::Html, routing::get, Router};
|
{{#include ../../../examples/hello_world_ssr.rs:main}}
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
|
|
||||||
println!("listening on http://{}", addr);
|
|
||||||
|
|
||||||
axum::Server::bind(&addr)
|
|
||||||
.serve(
|
|
||||||
Router::new()
|
|
||||||
.route("/", get(app_endpoint))
|
|
||||||
.into_make_service(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And then add our endpoint. We can either render `rsx!` directly:
|
And then add our endpoint. We can either render `rsx!` directly:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn app_endpoint() -> Html<String> {
|
{{#include ../../../examples/hello_world_ssr.rs:endpoint}}
|
||||||
// render the rsx! macro to HTML
|
|
||||||
Html(dioxus_ssr::render_lazy(rsx! {
|
|
||||||
div { "hello world!" }
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or we can render VirtualDoms.
|
Or we can render VirtualDoms.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn app_endpoint() -> Html<String> {
|
{{#include ../../../examples/hello_world_ssr.rs:second_endpoint}}
|
||||||
// create a component that renders a div with the text "hello world"
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx!(div { "hello world" }))
|
|
||||||
}
|
|
||||||
// create a VirtualDom with the app component
|
|
||||||
let mut app = VirtualDom::new(app);
|
|
||||||
// rebuild the VirtualDom before rendering
|
|
||||||
let _ = app.rebuild();
|
|
||||||
|
|
||||||
// render the VirtualDom to HTML
|
|
||||||
Html(dioxus_ssr::render_vdom(&app))
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
And then add our app component:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{{#include ../../../examples/hello_world_ssr.rs:component}}
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's it!
|
||||||
|
|
||||||
|
|
||||||
## Multithreaded Support
|
## Multithreaded Support
|
||||||
|
|
||||||
The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.
|
The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ This is only possible because the two hooks are always called in the same order,
|
|||||||
|
|
||||||
1. Hooks may be only used in components or other hooks (we'll get to that later)
|
1. Hooks may be only used in components or other hooks (we'll get to that later)
|
||||||
2. On every call to the component function
|
2. On every call to the component function
|
||||||
1. The same hooks must be called
|
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
|
||||||
2. In the same order
|
2. In the same order
|
||||||
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
|
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
web-component {
|
||||||
|
"my-prop": "5%",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
On Linux, install the following packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test --workspace --tests
|
||||||
|
```
|
||||||
@@ -38,6 +38,7 @@ slab = "0.4"
|
|||||||
|
|
||||||
futures-util = "0.3.25"
|
futures-util = "0.3.25"
|
||||||
rfd = "0.11.3"
|
rfd = "0.11.3"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
[target.'cfg(target_os = "ios")'.dependencies]
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fn module_loader(root_name: &str) -> String {
|
|||||||
let rootname = "{root_name}";
|
let rootname = "{root_name}";
|
||||||
let root = window.document.getElementById(rootname);
|
let root = window.document.getElementById(rootname);
|
||||||
if (root != null) {{
|
if (root != null) {{
|
||||||
window.interpreter = new Interpreter(root);
|
window.interpreter = new Interpreter(root, new InterpreterConfig(true));
|
||||||
window.ipc.postMessage(serializeIpcMessage("initialize"));
|
window.ipc.postMessage(serializeIpcMessage("initialize"));
|
||||||
}}
|
}}
|
||||||
</script>
|
</script>
|
||||||
@@ -87,7 +87,9 @@ pub(super) fn desktop_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Else, try to serve a file from the filesystem.
|
// Else, try to serve a file from the filesystem.
|
||||||
let path = PathBuf::from(request.uri().path().trim_start_matches('/'));
|
let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
|
||||||
|
.expect("expected URL to be UTF-8 encoded");
|
||||||
|
let path = PathBuf::from(&*decoded);
|
||||||
|
|
||||||
// If the path is relative, we'll try to serve it from the assets directory.
|
// If the path is relative, we'll try to serve it from the assets directory.
|
||||||
let mut asset = get_asset_root()
|
let mut asset = get_asset_root()
|
||||||
|
|||||||
@@ -41,17 +41,10 @@ impl Shortcut {
|
|||||||
|
|
||||||
impl ShortcutRegistry {
|
impl ShortcutRegistry {
|
||||||
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
|
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
|
||||||
let myself = Self {
|
Self {
|
||||||
manager: Rc::new(RefCell::new(ShortcutManager::new(target))),
|
manager: Rc::new(RefCell::new(ShortcutManager::new(target))),
|
||||||
shortcuts: Rc::new(RefCell::new(HashMap::new())),
|
shortcuts: Rc::new(RefCell::new(HashMap::new())),
|
||||||
};
|
}
|
||||||
// prevent CTRL+R from reloading the page which breaks apps
|
|
||||||
let _ = myself.add_shortcut(
|
|
||||||
Some(ModifiersState::CONTROL),
|
|
||||||
KeyCode::KeyR,
|
|
||||||
Box::new(|| {}),
|
|
||||||
);
|
|
||||||
myself
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn call_handlers(&self, id: AcceleratorId) {
|
pub(crate) fn call_handlers(&self, id: AcceleratorId) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ log = "0.4.14"
|
|||||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||||
criterion = "0.3.5"
|
criterion = "0.3.5"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.10.0"
|
||||||
tokio = { version = "1.21.2", features = ["full"] }
|
tokio = { version = "1.21.2", features = ["full"] }
|
||||||
# dioxus-edit-stream = { path = "../edit-stream" }
|
# dioxus-edit-stream = { path = "../edit-stream" }
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ impl<T: 'static> UseAtomRef<T> {
|
|||||||
self.value.borrow()
|
self.value.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is silent operation
|
||||||
|
/// call `.force_update()` manually if required
|
||||||
|
pub fn with_mut_silent(&self, cb: impl FnOnce(&mut T)) {
|
||||||
|
cb(&mut *self.write_silent())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write(&self) -> RefMut<T> {
|
pub fn write(&self) -> RefMut<T> {
|
||||||
self.root.force_update(self.ptr);
|
self.root.force_update(self.ptr);
|
||||||
self.value.borrow_mut()
|
self.value.borrow_mut()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr", "fullstack"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# server functions
|
# server functions
|
||||||
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", features = ["stable"] }
|
server_fn = { git = "https://github.com/leptos-rs/leptos", rev = "671b1e4a8fff7a2e05bb621ef08e87be2b18ccae", default-features = false, features = ["stable"] }
|
||||||
dioxus_server_macro = { path = "server-macro" }
|
dioxus_server_macro = { path = "server-macro" }
|
||||||
|
|
||||||
# warp
|
# warp
|
||||||
@@ -56,9 +56,11 @@ dioxus-hot-reload = { path = "../hot-reload" }
|
|||||||
web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload"]
|
default = ["hot-reload", "default-tls"]
|
||||||
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
|
hot-reload = ["serde_json", "tokio-stream", "futures-util"]
|
||||||
warp = ["dep:warp", "http-body", "ssr"]
|
warp = ["dep:warp", "http-body", "ssr"]
|
||||||
axum = ["dep:axum", "tower-http", "ssr"]
|
axum = ["dep:axum", "tower-http", "ssr"]
|
||||||
salvo = ["dep:salvo", "ssr"]
|
salvo = ["dep:salvo", "ssr"]
|
||||||
ssr = ["server_fn/ssr", "tokio", "dioxus-ssr", "hyper", "http"]
|
ssr = ["server_fn/ssr", "tokio", "dioxus-ssr", "hyper", "http"]
|
||||||
|
default-tls = ["server_fn/default-tls"]
|
||||||
|
rustls = ["server_fn/rustls"]
|
||||||
@@ -18,7 +18,7 @@ dioxus-html = { path = "../html", features = ["hot-reload-context"], version = "
|
|||||||
|
|
||||||
interprocess = { version = "1.2.1" }
|
interprocess = { version = "1.2.1" }
|
||||||
notify = "5.0.0"
|
notify = "5.0.0"
|
||||||
chrono = "0.4.23"
|
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
execute = "0.2.11"
|
execute = "0.2.11"
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ use web_sys::Element;
|
|||||||
|
|
||||||
#[wasm_bindgen(module = "/src/interpreter.js")]
|
#[wasm_bindgen(module = "/src/interpreter.js")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
pub type InterpreterConfig;
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(intercept_link_redirects: bool) -> InterpreterConfig;
|
||||||
|
|
||||||
pub type Interpreter;
|
pub type Interpreter;
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(arg: Element) -> Interpreter;
|
pub fn new(arg: Element, config: InterpreterConfig) -> Interpreter;
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
#[wasm_bindgen(method)]
|
||||||
pub fn SaveTemplate(this: &Interpreter, template: JsValue);
|
pub fn SaveTemplate(this: &Interpreter, template: JsValue);
|
||||||
|
|||||||
@@ -53,8 +53,15 @@ class ListenerMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InterpreterConfig {
|
||||||
|
constructor(intercept_link_redirects) {
|
||||||
|
this.intercept_link_redirects = intercept_link_redirects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Interpreter {
|
class Interpreter {
|
||||||
constructor(root) {
|
constructor(root, config) {
|
||||||
|
this.config = config;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.listeners = new ListenerMap(root);
|
this.listeners = new ListenerMap(root);
|
||||||
this.nodes = [root];
|
this.nodes = [root];
|
||||||
@@ -397,7 +404,7 @@ class Interpreter {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
|
this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
|
||||||
handler(event, edit.name, bubbles);
|
handler(event, edit.name, bubbles, this.config);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -407,32 +414,34 @@ class Interpreter {
|
|||||||
|
|
||||||
// this handler is only provided on the desktop and liveview implementations since this
|
// this handler is only provided on the desktop and liveview implementations since this
|
||||||
// method is not used by the web implementation
|
// method is not used by the web implementation
|
||||||
function handler(event, name, bubbles) {
|
function handler(event, name, bubbles, config) {
|
||||||
let target = event.target;
|
let target = event.target;
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
let preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
|
let preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
|
||||||
|
|
||||||
if (event.type === "click") {
|
if (event.type === "click") {
|
||||||
// todo call prevent default if it's the right type of event
|
// todo call prevent default if it's the right type of event
|
||||||
let a_element = target.closest("a");
|
if (config.intercept_link_redirects) {
|
||||||
if (a_element != null) {
|
let a_element = target.closest("a");
|
||||||
event.preventDefault();
|
if (a_element != null) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
let elementShouldPreventDefault =
|
let elementShouldPreventDefault =
|
||||||
preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
|
preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
|
||||||
let aElementShouldPreventDefault = a_element.getAttribute(
|
let aElementShouldPreventDefault = a_element.getAttribute(
|
||||||
`dioxus-prevent-default`
|
`dioxus-prevent-default`
|
||||||
);
|
);
|
||||||
let linkShouldPreventDefault =
|
let linkShouldPreventDefault =
|
||||||
aElementShouldPreventDefault &&
|
aElementShouldPreventDefault &&
|
||||||
aElementShouldPreventDefault.includes(`onclick`);
|
aElementShouldPreventDefault.includes(`onclick`);
|
||||||
|
|
||||||
if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
|
if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
|
||||||
const href = a_element.getAttribute("href");
|
const href = a_element.getAttribute("href");
|
||||||
if (href !== "" && href !== null && href !== undefined) {
|
if (href !== "" && href !== null && href !== undefined) {
|
||||||
window.ipc.postMessage(
|
window.ipc.postMessage(
|
||||||
serializeIpcMessage("browser_open", { href })
|
serializeIpcMessage("browser_open", { href })
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ once_cell = "1.17.1"
|
|||||||
# actix-ws = { version = "0.2.5", optional = true }
|
# actix-ws = { version = "0.2.5", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_env_logger = { version = "0.4.0" }
|
pretty_env_logger = { version = "0.5.0" }
|
||||||
tokio = { version = "1.22.0", features = ["full"] }
|
tokio = { version = "1.22.0", features = ["full"] }
|
||||||
dioxus = { path = "../dioxus", version = "0.3.0" }
|
dioxus = { path = "../dioxus", version = "0.3.0" }
|
||||||
warp = "0.3.3"
|
warp = "0.3.3"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function main() {
|
|||||||
class IPC {
|
class IPC {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
// connect to the websocket
|
// connect to the websocket
|
||||||
window.interpreter = new Interpreter(root);
|
window.interpreter = new Interpreter(root, new InterpreterConfig(false));
|
||||||
|
|
||||||
let ws = new WebSocket(WS_ADDR);
|
let ws = new WebSocket(WS_ADDR);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use dioxus_rsx::{
|
use dioxus_rsx::{
|
||||||
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, IfmtInput,
|
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
|
||||||
};
|
};
|
||||||
pub use html_parser::{Dom, Node};
|
pub use html_parser::{Dom, Node};
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
@@ -21,7 +21,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
|
Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
|
||||||
Node::Element(el) => {
|
Node::Element(el) => {
|
||||||
let el_name = el.name.to_case(Case::Snake);
|
let el_name = el.name.to_case(Case::Snake);
|
||||||
let el_name = Ident::new(el_name.as_str(), Span::call_site());
|
let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site()));
|
||||||
|
|
||||||
let mut attributes: Vec<_> = el
|
let mut attributes: Vec<_> = el
|
||||||
.attributes
|
.attributes
|
||||||
|
|||||||
+109
-12
@@ -1,9 +1,13 @@
|
|||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseBuffer, ParseStream},
|
parse::{Parse, ParseBuffer, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
spanned::Spanned,
|
||||||
Error, Expr, Ident, LitStr, Result, Token,
|
Error, Expr, Ident, LitStr, Result, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12,7 +16,7 @@ use syn::{
|
|||||||
// =======================================
|
// =======================================
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
pub name: Ident,
|
pub name: ElementName,
|
||||||
pub key: Option<IfmtInput>,
|
pub key: Option<IfmtInput>,
|
||||||
pub attributes: Vec<ElementAttrNamed>,
|
pub attributes: Vec<ElementAttrNamed>,
|
||||||
pub children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
@@ -22,7 +26,7 @@ pub struct Element {
|
|||||||
|
|
||||||
impl Parse for Element {
|
impl Parse for Element {
|
||||||
fn parse(stream: ParseStream) -> Result<Self> {
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
let el_name = Ident::parse(stream)?;
|
let el_name = ElementName::parse(stream)?;
|
||||||
|
|
||||||
// parse the guts
|
// parse the guts
|
||||||
let content: ParseBuffer;
|
let content: ParseBuffer;
|
||||||
@@ -181,7 +185,7 @@ impl ToTokens for Element {
|
|||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
__cx.element(
|
__cx.element(
|
||||||
dioxus_elements::#name,
|
#name,
|
||||||
__cx.bump().alloc([ #(#listeners),* ]),
|
__cx.bump().alloc([ #(#listeners),* ]),
|
||||||
__cx.bump().alloc([ #(#attr),* ]),
|
__cx.bump().alloc([ #(#attr),* ]),
|
||||||
__cx.bump().alloc([ #(#children),* ]),
|
__cx.bump().alloc([ #(#children),* ]),
|
||||||
@@ -191,6 +195,75 @@ impl ToTokens for Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
|
pub enum ElementName {
|
||||||
|
Ident(Ident),
|
||||||
|
Custom(LitStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementName {
|
||||||
|
pub(crate) fn tag_name(&self) -> TokenStream2 {
|
||||||
|
match self {
|
||||||
|
ElementName::Ident(i) => quote! { dioxus_elements::#i::TAG_NAME },
|
||||||
|
ElementName::Custom(s) => quote! { #s },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementName {
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
ElementName::Ident(i) => i.span(),
|
||||||
|
ElementName::Custom(s) => s.span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for ElementName {
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
match self {
|
||||||
|
ElementName::Ident(i) => i == *other,
|
||||||
|
ElementName::Custom(s) => s.value() == *other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ElementName {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ElementName::Ident(i) => write!(f, "{}", i),
|
||||||
|
ElementName::Custom(s) => write!(f, "{}", s.value()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ElementName {
|
||||||
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
|
let raw = Punctuated::<Ident, Token![-]>::parse_separated_nonempty(stream)?;
|
||||||
|
if raw.len() == 1 {
|
||||||
|
Ok(ElementName::Ident(raw.into_iter().next().unwrap()))
|
||||||
|
} else {
|
||||||
|
let span = raw.span();
|
||||||
|
let tag = raw
|
||||||
|
.into_iter()
|
||||||
|
.map(|ident| ident.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("-");
|
||||||
|
let tag = LitStr::new(&tag, span);
|
||||||
|
Ok(ElementName::Custom(tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ElementName {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
match self {
|
||||||
|
ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::#i }),
|
||||||
|
ElementName::Custom(s) => tokens.append_all(quote! { #s }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
pub enum ElementAttr {
|
pub enum ElementAttr {
|
||||||
/// `attribute: "value"`
|
/// `attribute: "value"`
|
||||||
@@ -234,7 +307,7 @@ impl ElementAttr {
|
|||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
pub struct ElementAttrNamed {
|
pub struct ElementAttrNamed {
|
||||||
pub el_name: Ident,
|
pub el_name: ElementName,
|
||||||
pub attr: ElementAttr,
|
pub attr: ElementAttr,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,24 +315,46 @@ impl ToTokens for ElementAttrNamed {
|
|||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let ElementAttrNamed { el_name, attr } = self;
|
let ElementAttrNamed { el_name, attr } = self;
|
||||||
|
|
||||||
tokens.append_all(match attr {
|
let ns = |name| match el_name {
|
||||||
|
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name.1 },
|
||||||
|
ElementName::Custom(_) => quote! { None },
|
||||||
|
};
|
||||||
|
let volitile = |name| match el_name {
|
||||||
|
ElementName::Ident(_) => quote! { #el_name::#name.2 },
|
||||||
|
ElementName::Custom(_) => quote! { false },
|
||||||
|
};
|
||||||
|
let attribute = |name: &Ident| match el_name {
|
||||||
|
ElementName::Ident(_) => quote! { #el_name::#name.0 },
|
||||||
|
ElementName::Custom(_) => {
|
||||||
|
let as_string = name.to_string();
|
||||||
|
quote!(#as_string)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let attribute = match attr {
|
||||||
ElementAttr::AttrText { name, value } => {
|
ElementAttr::AttrText { name, value } => {
|
||||||
|
let ns = ns(name);
|
||||||
|
let volitile = volitile(name);
|
||||||
|
let attribute = attribute(name);
|
||||||
quote! {
|
quote! {
|
||||||
__cx.attr(
|
__cx.attr(
|
||||||
dioxus_elements::#el_name::#name.0,
|
#attribute,
|
||||||
#value,
|
#value,
|
||||||
dioxus_elements::#el_name::#name.1,
|
#ns,
|
||||||
dioxus_elements::#el_name::#name.2
|
#volitile
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ElementAttr::AttrExpression { name, value } => {
|
ElementAttr::AttrExpression { name, value } => {
|
||||||
|
let ns = ns(name);
|
||||||
|
let volitile = volitile(name);
|
||||||
|
let attribute = attribute(name);
|
||||||
quote! {
|
quote! {
|
||||||
__cx.attr(
|
__cx.attr(
|
||||||
dioxus_elements::#el_name::#name.0,
|
#attribute,
|
||||||
#value,
|
#value,
|
||||||
dioxus_elements::#el_name::#name.1,
|
#ns,
|
||||||
dioxus_elements::#el_name::#name.2
|
#volitile
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,7 +383,9 @@ impl ToTokens for ElementAttrNamed {
|
|||||||
dioxus_elements::events::#name(__cx, #tokens)
|
dioxus_elements::events::#name(__cx, #tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
tokens.append_all(attribute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -426,13 +426,25 @@ impl<'a> DynamicContext<'a> {
|
|||||||
match root {
|
match root {
|
||||||
BodyNode::Element(el) => {
|
BodyNode::Element(el) => {
|
||||||
let el_name = &el.name;
|
let el_name = &el.name;
|
||||||
|
let ns = |name| match el_name {
|
||||||
|
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
|
||||||
|
ElementName::Custom(_) => quote! { None },
|
||||||
|
};
|
||||||
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
|
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
|
||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||||
let value = value.to_static().unwrap();
|
let value = value.to_static().unwrap();
|
||||||
|
let ns = ns(quote!(#name.1));
|
||||||
|
let name = match el_name {
|
||||||
|
ElementName::Ident(_) => quote! { #el_name::#name.0 },
|
||||||
|
ElementName::Custom(_) => {
|
||||||
|
let as_string = name.to_string();
|
||||||
|
quote! { #as_string }
|
||||||
|
}
|
||||||
|
};
|
||||||
quote! {
|
quote! {
|
||||||
::dioxus::core::TemplateAttribute::Static {
|
::dioxus::core::TemplateAttribute::Static {
|
||||||
name: dioxus_elements::#el_name::#name.0,
|
name: #name,
|
||||||
namespace: dioxus_elements::#el_name::#name.1,
|
namespace: #ns,
|
||||||
value: #value,
|
value: #value,
|
||||||
|
|
||||||
// todo: we don't diff these so we never apply the volatile flag
|
// todo: we don't diff these so we never apply the volatile flag
|
||||||
@@ -479,10 +491,13 @@ impl<'a> DynamicContext<'a> {
|
|||||||
let _opt = el.children.len() == 1;
|
let _opt = el.children.len() == 1;
|
||||||
let children = quote! { #(#children),* };
|
let children = quote! { #(#children),* };
|
||||||
|
|
||||||
|
let ns = ns(quote!(NAME_SPACE));
|
||||||
|
let el_name = el_name.tag_name();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
::dioxus::core::TemplateNode::Element {
|
::dioxus::core::TemplateNode::Element {
|
||||||
tag: dioxus_elements::#el_name::TAG_NAME,
|
tag: #el_name,
|
||||||
namespace: dioxus_elements::#el_name::NAME_SPACE,
|
namespace: #ns,
|
||||||
attrs: &[ #attrs ],
|
attrs: &[ #attrs ],
|
||||||
children: &[ #children ],
|
children: &[ #children ],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,16 @@ impl Parse for BodyNode {
|
|||||||
return Ok(BodyNode::Text(stream.parse()?));
|
return Ok(BodyNode::Text(stream.parse()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this is a dash-separated path, it's a web component (custom element)
|
||||||
let body_stream = stream.fork();
|
let body_stream = stream.fork();
|
||||||
|
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
|
||||||
|
if name.value().contains('-') && body_stream.peek(token::Brace) {
|
||||||
|
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_stream = stream.fork();
|
||||||
|
|
||||||
if let Ok(path) = body_stream.parse::<syn::Path>() {
|
if let Ok(path) = body_stream.parse::<syn::Path>() {
|
||||||
// this is an Element if path match of:
|
// this is an Element if path match of:
|
||||||
// - one ident
|
// - one ident
|
||||||
|
|||||||
Reference in new issue
Block a user