mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
Merge branch 'upstream' into simplify-native-core
This commit is contained in:
commit
5aa3587ea9
92 changed files with 2090 additions and 1094 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -104,7 +104,7 @@ jobs:
|
|||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace -- -D warnings
|
||||
args: --workspace --examples --tests -- -D warnings
|
||||
|
||||
# Coverage is disabled until we can fix it
|
||||
# coverage:
|
||||
|
|
123
.github/workflows/miri.yml
vendored
Normal file
123
.github/workflows/miri.yml
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
name: Miri Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run in PRs and for bors, but not on master.
|
||||
branches:
|
||||
- 'auto'
|
||||
- 'try'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
schedule:
|
||||
- cron: '6 6 * * *' # At 6:06 UTC every day.
|
||||
|
||||
env:
|
||||
CARGO_UNSTABLE_SPARSE_REGISTRY: 'true'
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2022-11-03
|
||||
rust_clippy: 1.65.0
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
# - tokio/README.md
|
||||
# - CONTRIBUTING.md
|
||||
# - tokio/Cargo.toml
|
||||
# - tokio-util/Cargo.toml
|
||||
# - tokio-test/Cargo.toml
|
||||
# - tokio-stream/Cargo.toml
|
||||
# rust_min: 1.49.0
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
HOST_TARGET: ${{ matrix.host_target }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
host_target: x86_64-unknown-linux-gnu
|
||||
# - os: macos-latest
|
||||
# host_target: x86_64-apple-darwin
|
||||
# - os: windows-latest
|
||||
# host_target: i686-pc-windows-msvc
|
||||
# - os: windows-latest
|
||||
# host_target: i686-pc-windows-msvc
|
||||
# - os: windows-latest
|
||||
# host_target: i686-pc-windows-msvc
|
||||
# - os: windows-latest
|
||||
# host_target: i686-pc-windows-msvc
|
||||
steps:
|
||||
- name: Set the tag GC interval to 1 on linux
|
||||
if: runner.os == 'Linux'
|
||||
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: miri
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: miri
|
||||
# Many of tests in tokio/tests and doctests use #[tokio::test] or
|
||||
# #[tokio::main] that calls epoll_create1 that Miri does not support.
|
||||
# run: cargo miri test --features full --lib --no-fail-fast
|
||||
run: |
|
||||
cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture
|
||||
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
|
||||
|
||||
# working-directory: tokio
|
||||
env:
|
||||
# todo: disable memory leaks ignore
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
|
||||
PROPTEST_CASES: 10
|
||||
|
||||
# Cache the global cargo directory, but NOT the local `target` directory which
|
||||
# we cannot reuse anyway when the nightly changes (and it grows quite large
|
||||
# over time).
|
||||
# - name: Add cache for cargo
|
||||
# id: cache
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: |
|
||||
# # Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
|
||||
# ~/.cargo/bin
|
||||
# ~/.cargo/registry/index
|
||||
# ~/.cargo/registry/cache
|
||||
# ~/.cargo/git/db
|
||||
# # contains package information of crates installed via `cargo install`.
|
||||
# ~/.cargo/.crates.toml
|
||||
# ~/.cargo/.crates2.json
|
||||
# key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
# restore-keys: ${{ runner.os }}-cargo
|
||||
|
||||
# - name: Install rustup-toolchain-install-master
|
||||
# if: ${{ steps.cache.outputs.cache-hit != 'true' }}
|
||||
# shell: bash
|
||||
# run: |
|
||||
# cargo install -f rustup-toolchain-install-master
|
||||
# - name: Install "master" toolchain
|
||||
# shell: bash
|
||||
# run: |
|
||||
# if [[ ${{ github.event_name }} == 'schedule' ]]; then
|
||||
# echo "Building against latest rustc git version"
|
||||
# git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1 > rust-version
|
||||
# fi
|
||||
# toolchain --host ${{ matrix.host_target }}
|
||||
# - name: Show Rust version
|
||||
# run: |
|
||||
# rustup show
|
||||
# rustc -Vv
|
||||
# cargo -V
|
||||
# - name: Test
|
||||
# run: |
|
||||
# MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks" cargo +nightly miri test --package dioxus-core --test miri_stress -- --exact --nocapture
|
||||
# MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks" cargo +nightly miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = use_window(cx);
|
||||
let emails_sent = use_ref(cx, || vec![]);
|
||||
let emails_sent = use_ref(cx, Vec::new);
|
||||
|
||||
let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| {
|
||||
to_owned![emails_sent];
|
||||
|
@ -56,7 +56,7 @@ struct ComposeProps {
|
|||
}
|
||||
|
||||
fn compose(cx: Scope<ComposeProps>) -> Element {
|
||||
let user_input = use_state(cx, || String::new());
|
||||
let user_input = use_state(cx, String::new);
|
||||
let window = use_window(cx);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
//! This example shows to wrap a webcomponent / custom element with a component.
|
||||
//!
|
||||
//! Oftentimes, a third party library will provide a webcomponent that you want
|
||||
//! to use in your application. This example shows how to create that custom element
|
||||
//! directly with the raw_element method on NodeFactory.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let _ = dom.rebuild();
|
||||
|
||||
let output = dioxus_ssr::render(&dom);
|
||||
|
||||
println!("{}", output);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let g = cx.component(component, (), "component");
|
||||
let c = cx.make_node(g);
|
||||
cx.render(rsx! {
|
||||
div { c }
|
||||
})
|
||||
|
||||
// let nf = NodeFactory::new(cx);
|
||||
|
||||
// let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
|
||||
|
||||
// attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
|
||||
|
||||
// attrs.push(nf.attr("name", format_args!("bob"), None, false));
|
||||
|
||||
// attrs.push(nf.attr("age", format_args!("47"), None, false));
|
||||
|
||||
// Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
|
||||
}
|
||||
|
||||
fn component(cx: Scope) -> Element {
|
||||
todo!()
|
||||
}
|
36
examples/drops.rs
Normal file
36
examples/drops.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = if cx.generation() % 2 == 0 { 10 } else { 0 };
|
||||
|
||||
println!("Generation: {}", cx.generation());
|
||||
|
||||
if cx.generation() < 10 {
|
||||
cx.needs_update();
|
||||
}
|
||||
|
||||
render! {
|
||||
(0..count).map(|_| rsx!{
|
||||
drop_child {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_child(cx: Scope) -> Element {
|
||||
cx.use_hook(|| Drops);
|
||||
render! {
|
||||
div{}
|
||||
}
|
||||
}
|
||||
|
||||
struct Drops;
|
||||
|
||||
impl Drop for Drops {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropped!");
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ fn main() {
|
|||
static NAME: Atom<String> = |_| "world".to_string();
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
use_init_atom_root(cx);
|
||||
let name = use_read(cx, NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{use_window, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = use_window(cx);
|
||||
let window = dioxus_desktop::use_window(cx);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, Config, LogicalSize, WindowBuilder};
|
||||
use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, LogicalSize, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch_cfg(app, make_config());
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::*;
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.2"}
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.2" }
|
||||
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
triple_accel = "0.4.0"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
prettyplease = { git = "https://github.com/DioxusLabs/prettyplease-macro-fmt.git", features = [
|
||||
"verbatim",
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus-autofmt) |
|
||||
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -45,5 +45,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -1,33 +1,18 @@
|
|||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
//! The output buffer that supports some helpful methods
|
||||
//! These are separate from the input so we can lend references between the two
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
use dioxus_rsx::IfmtInput;
|
||||
|
||||
/// The output buffer that tracks indent and string
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Buffer {
|
||||
pub src: Vec<String>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub buf: String,
|
||||
pub indent: usize,
|
||||
pub comments: VecDeque<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
|
@ -62,160 +47,14 @@ impl Buffer {
|
|||
writeln!(self.buf)
|
||||
}
|
||||
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el),
|
||||
BodyNode::Component(component) => self.write_component(component),
|
||||
BodyNode::Text(text) => self.write_text(text),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_text(&mut self, text: &IfmtInput) -> Result {
|
||||
write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.buf)
|
||||
}
|
||||
|
||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||
// collect all comments upwards
|
||||
let start = child.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
|
||||
if line.trim().starts_with("//") || line.is_empty() {
|
||||
if id != 0 {
|
||||
self.comments.push_front(id);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_was_empty = false;
|
||||
while let Some(comment_line) = self.comments.pop_front() {
|
||||
let line = &self.src[comment_line];
|
||||
if line.is_empty() {
|
||||
if !last_was_empty {
|
||||
self.new_line()?;
|
||||
}
|
||||
last_was_empty = true;
|
||||
} else {
|
||||
last_was_empty = false;
|
||||
self.tabbed_line()?;
|
||||
write!(self.buf, "{}", self.src[comment_line].trim())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||
self.indent += 1;
|
||||
|
||||
self.write_body_no_indent(children)?;
|
||||
|
||||
self.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
|
||||
for (idx, child) in children.iter().enumerate() {
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.buf, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
_ => break 'line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::EventTokens { tokens, name } => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||
self.cached_formats.entry(location)
|
||||
{
|
||||
let formatted = prettyplease::unparse_expr(tokens);
|
||||
let len = if formatted.contains('\n') {
|
||||
10000
|
||||
} else {
|
||||
formatted.len()
|
||||
};
|
||||
e.insert(formatted);
|
||||
len
|
||||
} else {
|
||||
self.cached_formats[&location].len()
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
self.cached_formats
|
||||
.entry(Location::new(expr.span().start()))
|
||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
||||
.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
fn line_length(&self) -> usize {
|
||||
self.end().line - self.start().line
|
||||
impl std::fmt::Write for Buffer {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.buf.push_str(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
198
packages/autofmt/src/collect_macros.rs
Normal file
198
packages/autofmt/src/collect_macros.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
//! Collect macros from a file
|
||||
//!
|
||||
//! Returns all macros that match a pattern. You can use this information to autoformat them later
|
||||
|
||||
use proc_macro2::LineColumn;
|
||||
use syn::{Block, Expr, File, Item, Macro, Stmt};
|
||||
|
||||
type CollectedMacro<'a> = &'a Macro;
|
||||
|
||||
pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) {
|
||||
for item in file.items.iter() {
|
||||
collect_from_item(item, macros);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec<CollectedMacro<'a>>) {
|
||||
match item {
|
||||
Item::Fn(f) => collect_from_block(&f.block, macros),
|
||||
|
||||
// Ignore macros if they're not rsx or render
|
||||
Item::Macro(macro_) => {
|
||||
if macro_.mac.path.segments[0].ident == "rsx"
|
||||
|| macro_.mac.path.segments[0].ident == "render"
|
||||
{
|
||||
macros.push(¯o_.mac);
|
||||
}
|
||||
}
|
||||
|
||||
// Currently disabled since we're not focused on autoformatting these
|
||||
Item::Impl(_imp) => {}
|
||||
Item::Trait(_) => {}
|
||||
|
||||
// Global-ish things
|
||||
Item::Static(f) => collect_from_expr(&f.expr, macros),
|
||||
Item::Const(f) => collect_from_expr(&f.expr, macros),
|
||||
Item::Mod(s) => {
|
||||
if let Some((_, block)) = &s.content {
|
||||
for item in block {
|
||||
collect_from_item(item, macros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// None of these we can really do anything with at the item level
|
||||
Item::Macro2(_)
|
||||
| Item::Enum(_)
|
||||
| Item::ExternCrate(_)
|
||||
| Item::ForeignMod(_)
|
||||
| Item::TraitAlias(_)
|
||||
| Item::Type(_)
|
||||
| Item::Struct(_)
|
||||
| Item::Union(_)
|
||||
| Item::Use(_)
|
||||
| Item::Verbatim(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_from_block<'a>(block: &'a Block, macros: &mut Vec<CollectedMacro<'a>>) {
|
||||
for stmt in &block.stmts {
|
||||
match stmt {
|
||||
Stmt::Item(item) => collect_from_item(item, macros),
|
||||
Stmt::Local(local) => {
|
||||
if let Some((_eq, init)) = &local.init {
|
||||
collect_from_expr(init, macros);
|
||||
}
|
||||
}
|
||||
Stmt::Expr(exp) | Stmt::Semi(exp, _) => collect_from_expr(exp, macros),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec<CollectedMacro<'a>>) {
|
||||
// collect an expr from the exprs, descending into blocks
|
||||
match expr {
|
||||
Expr::Macro(macro_) => {
|
||||
if macro_.mac.path.segments[0].ident == "rsx"
|
||||
|| macro_.mac.path.segments[0].ident == "render"
|
||||
{
|
||||
macros.push(¯o_.mac);
|
||||
}
|
||||
}
|
||||
|
||||
Expr::MethodCall(e) => {
|
||||
collect_from_expr(&e.receiver, macros);
|
||||
for expr in e.args.iter() {
|
||||
collect_from_expr(expr, macros);
|
||||
}
|
||||
}
|
||||
Expr::Assign(exp) => {
|
||||
collect_from_expr(&exp.left, macros);
|
||||
collect_from_expr(&exp.right, macros);
|
||||
}
|
||||
|
||||
Expr::Async(b) => collect_from_block(&b.block, macros),
|
||||
Expr::Block(b) => collect_from_block(&b.block, macros),
|
||||
Expr::Closure(c) => collect_from_expr(&c.body, macros),
|
||||
Expr::Let(l) => collect_from_expr(&l.expr, macros),
|
||||
Expr::Unsafe(u) => collect_from_block(&u.block, macros),
|
||||
Expr::Loop(l) => collect_from_block(&l.body, macros),
|
||||
|
||||
Expr::Call(c) => {
|
||||
collect_from_expr(&c.func, macros);
|
||||
for expr in c.args.iter() {
|
||||
collect_from_expr(expr, macros);
|
||||
}
|
||||
}
|
||||
|
||||
Expr::ForLoop(b) => {
|
||||
collect_from_expr(&b.expr, macros);
|
||||
collect_from_block(&b.body, macros);
|
||||
}
|
||||
Expr::If(f) => {
|
||||
collect_from_expr(&f.cond, macros);
|
||||
collect_from_block(&f.then_branch, macros);
|
||||
if let Some((_, else_branch)) = &f.else_branch {
|
||||
collect_from_expr(else_branch, macros);
|
||||
}
|
||||
}
|
||||
Expr::Yield(y) => {
|
||||
if let Some(expr) = &y.expr {
|
||||
collect_from_expr(expr, macros);
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Return(r) => {
|
||||
if let Some(expr) = &r.expr {
|
||||
collect_from_expr(expr, macros);
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Match(l) => {
|
||||
collect_from_expr(&l.expr, macros);
|
||||
for arm in l.arms.iter() {
|
||||
if let Some((_, expr)) = &arm.guard {
|
||||
collect_from_expr(expr, macros);
|
||||
}
|
||||
|
||||
collect_from_expr(&arm.body, macros);
|
||||
}
|
||||
}
|
||||
|
||||
Expr::While(w) => {
|
||||
collect_from_expr(&w.cond, macros);
|
||||
collect_from_block(&w.body, macros);
|
||||
}
|
||||
|
||||
// don't both formatting these for now
|
||||
Expr::Array(_)
|
||||
| Expr::AssignOp(_)
|
||||
| Expr::Await(_)
|
||||
| Expr::Binary(_)
|
||||
| Expr::Box(_)
|
||||
| Expr::Break(_)
|
||||
| Expr::Cast(_)
|
||||
| Expr::Continue(_)
|
||||
| Expr::Field(_)
|
||||
| Expr::Group(_)
|
||||
| Expr::Index(_)
|
||||
| Expr::Lit(_)
|
||||
| Expr::Paren(_)
|
||||
| Expr::Path(_)
|
||||
| Expr::Range(_)
|
||||
| Expr::Reference(_)
|
||||
| Expr::Repeat(_)
|
||||
| Expr::Struct(_)
|
||||
| Expr::Try(_)
|
||||
| Expr::TryBlock(_)
|
||||
| Expr::Tuple(_)
|
||||
| Expr::Type(_)
|
||||
| Expr::Unary(_)
|
||||
| Expr::Verbatim(_) => {}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
||||
let mut offset = 0;
|
||||
for _ in 1..location.line {
|
||||
offset += input[offset..].find('\n').unwrap() + 1;
|
||||
}
|
||||
offset
|
||||
+ input[offset..]
|
||||
.chars()
|
||||
.take(location.column)
|
||||
.map(char::len_utf8)
|
||||
.sum::<usize>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_file_and_collects_rsx_macros() {
|
||||
let contents = include_str!("../tests/samples/long.rsx");
|
||||
let parsed = syn::parse_file(contents).unwrap();
|
||||
let mut macros = vec![];
|
||||
collect_from_file(&parsed, &mut macros);
|
||||
assert_eq!(macros.len(), 3);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{buffer::Location, Buffer};
|
||||
use crate::{writer::Location, Writer};
|
||||
use dioxus_rsx::*;
|
||||
use quote::ToTokens;
|
||||
use std::fmt::{Result, Write};
|
||||
|
@ -19,7 +19,7 @@ enum ShortOptimization {
|
|||
NoOpt,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
impl Writer<'_> {
|
||||
pub fn write_component(
|
||||
&mut self,
|
||||
Component {
|
||||
|
@ -28,6 +28,7 @@ impl Buffer {
|
|||
children,
|
||||
manual_props,
|
||||
prop_gen_args,
|
||||
..
|
||||
}: &Component,
|
||||
) -> Result {
|
||||
self.write_component_name(name, prop_gen_args)?;
|
||||
|
@ -82,46 +83,46 @@ impl Buffer {
|
|||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_component_fields(fields, manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for child in children {
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
self.write_component_fields(fields, manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
self.write_body_indented(children)?;
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_component_fields(fields, manual_props, false)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
self.write_body_indented(children)?;
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, "}}")?;
|
||||
write!(self.out, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -133,16 +134,16 @@ impl Buffer {
|
|||
let mut name = name.to_token_stream().to_string();
|
||||
name.retain(|c| !c.is_whitespace());
|
||||
|
||||
write!(self.buf, "{name}")?;
|
||||
write!(self.out, "{name}")?;
|
||||
|
||||
if let Some(generics) = generics {
|
||||
let mut written = generics.to_token_stream().to_string();
|
||||
written.retain(|c| !c.is_whitespace());
|
||||
|
||||
write!(self.buf, "{}", written)?;
|
||||
write!(self.out, "{}", written)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " {{")?;
|
||||
write!(self.out, " {{")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -157,18 +158,18 @@ impl Buffer {
|
|||
|
||||
while let Some(field) = field_iter.next() {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
let name = &field.name;
|
||||
match &field.content {
|
||||
ContentField::ManExpr(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
write!(self.out, "{}: {}", name, out)?;
|
||||
}
|
||||
ContentField::Formatted(s) => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}: \"{}\"",
|
||||
name,
|
||||
s.source.as_ref().unwrap().value()
|
||||
|
@ -178,27 +179,27 @@ impl Buffer {
|
|||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
write!(self.out, "{}: {}", name, first)?;
|
||||
for line in lines {
|
||||
self.new_line()?;
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
self.out.new_line()?;
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{}", line)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if field_iter.peek().is_some() || manual_props.is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exp) = manual_props {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
self.write_manual_props(exp)?;
|
||||
}
|
||||
|
@ -258,10 +259,10 @@ impl Buffer {
|
|||
|
||||
let first_line = lines.next().unwrap();
|
||||
|
||||
write!(self.buf, "..{first_line}")?;
|
||||
write!(self.out, "..{first_line}")?;
|
||||
for line in lines {
|
||||
self.indented_tabbed_line()?;
|
||||
write!(self.buf, "{line}")?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
write!(self.out, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,35 +1,56 @@
|
|||
use crate::Buffer;
|
||||
use crate::Writer;
|
||||
use dioxus_rsx::*;
|
||||
use proc_macro2::Span;
|
||||
use std::{fmt::Result, fmt::Write};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
use std::{
|
||||
fmt::Result,
|
||||
fmt::{self, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, token::Brace, Expr};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ShortOptimization {
|
||||
// Special because we want to print the closing bracket immediately
|
||||
/// Special because we want to print the closing bracket immediately
|
||||
///
|
||||
/// IE
|
||||
/// `div {}` instead of `div { }`
|
||||
Empty,
|
||||
|
||||
// Special optimization to put everything on the same line
|
||||
/// Special optimization to put everything on the same line and add some buffer spaces
|
||||
///
|
||||
/// IE
|
||||
///
|
||||
/// `div { "asdasd" }` instead of a multiline variant
|
||||
Oneliner,
|
||||
|
||||
// Optimization where children flow but props remain fixed on top
|
||||
/// Optimization where children flow but props remain fixed on top
|
||||
PropsOnTop,
|
||||
|
||||
// The noisiest optimization where everything flows
|
||||
/// The noisiest optimization where everything flows
|
||||
NoOpt,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn write_element(
|
||||
&mut self,
|
||||
Element {
|
||||
/*
|
||||
// whitespace
|
||||
div {
|
||||
// some whitespace
|
||||
class: "asdasd"
|
||||
|
||||
// whjiot
|
||||
asdasd // whitespace
|
||||
}
|
||||
*/
|
||||
|
||||
impl Writer<'_> {
|
||||
pub fn write_element(&mut self, el: &Element) -> Result {
|
||||
let Element {
|
||||
name,
|
||||
key,
|
||||
attributes,
|
||||
children,
|
||||
_is_static,
|
||||
}: &Element,
|
||||
) -> Result {
|
||||
brace,
|
||||
} = el;
|
||||
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
|
@ -37,7 +58,7 @@ impl Buffer {
|
|||
4. Write the children
|
||||
*/
|
||||
|
||||
write!(self.buf, "{name} {{")?;
|
||||
write!(self.out, "{name} {{")?;
|
||||
|
||||
// decide if we have any special optimizations
|
||||
// Default with none, opt the cases in one-by-one
|
||||
|
@ -45,8 +66,9 @@ impl Buffer {
|
|||
|
||||
// check if we have a lot of attributes
|
||||
let attr_len = self.is_short_attrs(attributes);
|
||||
let is_short_attr_list = attr_len < 80;
|
||||
let is_small_children = self.is_short_children(children).is_some();
|
||||
let is_short_attr_list = (attr_len + self.out.indent * 4) < 80;
|
||||
let children_len = self.is_short_children(children);
|
||||
let is_small_children = children_len.is_some();
|
||||
|
||||
// if we have few attributes and a lot of children, place the attrs on top
|
||||
if is_short_attr_list && !is_small_children {
|
||||
|
@ -64,12 +86,19 @@ impl Buffer {
|
|||
|
||||
// if we have few children and few attributes, make it a one-liner
|
||||
if is_short_attr_list && is_small_children {
|
||||
opt_level = ShortOptimization::Oneliner;
|
||||
if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 {
|
||||
opt_level = ShortOptimization::Oneliner;
|
||||
} else {
|
||||
opt_level = ShortOptimization::PropsOnTop;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's nothing at all, empty optimization
|
||||
if attributes.is_empty() && children.is_empty() && key.is_none() {
|
||||
opt_level = ShortOptimization::Empty;
|
||||
|
||||
// Write comments if they exist
|
||||
self.write_todo_body(brace)?;
|
||||
}
|
||||
|
||||
// multiline handlers bump everything down
|
||||
|
@ -80,55 +109,56 @@ impl Buffer {
|
|||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_attributes(attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for (id, child) in children.iter().enumerate() {
|
||||
self.write_ident(child)?;
|
||||
if id != children.len() - 1 && children.len() > 1 {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
if !attributes.is_empty() || key.is_some() {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
self.write_attributes(attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_attributes(attributes, key, false)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, "}}")?;
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -143,39 +173,39 @@ impl Buffer {
|
|||
|
||||
if let Some(key) = key {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"key: \"{}\"",
|
||||
key.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
if !attributes.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
self.indent += 1;
|
||||
self.out.indent += 1;
|
||||
if !sameline {
|
||||
self.write_comments(attr.attr.start())?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.out.indent -= 1;
|
||||
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
self.write_attribute(attr)?;
|
||||
|
||||
if attr_iter.peek().is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,19 +217,19 @@ impl Buffer {
|
|||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{name}: \"{value}\"",
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
write!(self.out, "{}: {}", name, out)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"\"{name}\": \"{value}\"",
|
||||
name = name.value(),
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
|
@ -208,7 +238,7 @@ impl Buffer {
|
|||
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "\"{}\": {}", name.value(), out)?;
|
||||
write!(self.out, "\"{}\": {}", name.value(), out)?;
|
||||
}
|
||||
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
|
@ -220,17 +250,17 @@ impl Buffer {
|
|||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
write!(self.out, "{}: {}", name, first)?;
|
||||
} else {
|
||||
writeln!(self.buf, "{}: {}", name, first)?;
|
||||
writeln!(self.out, "{}: {}", name, first)?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{}", line)?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "")?;
|
||||
write!(self.out, "")?;
|
||||
} else {
|
||||
writeln!(self.buf)?;
|
||||
writeln!(self.out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +276,7 @@ impl Buffer {
|
|||
let start = location.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
let this_line = self.src[line_start].as_str();
|
||||
let this_line = self.src[line_start];
|
||||
|
||||
let beginning = if this_line.len() > start.column {
|
||||
this_line[..start.column].trim()
|
||||
|
@ -254,8 +284,6 @@ impl Buffer {
|
|||
""
|
||||
};
|
||||
|
||||
// dbg!(beginning);
|
||||
|
||||
beginning.is_empty()
|
||||
}
|
||||
|
||||
|
@ -268,6 +296,10 @@ impl Buffer {
|
|||
if children.is_empty() {
|
||||
// todo: allow elements with comments but no children
|
||||
// like div { /* comment */ }
|
||||
// or
|
||||
// div {
|
||||
// // some helpful
|
||||
// }
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
|
@ -333,8 +365,8 @@ impl Buffer {
|
|||
Some(len) => total_count += len,
|
||||
None => return None,
|
||||
},
|
||||
BodyNode::ForLoop(_) => todo!(),
|
||||
BodyNode::IfChain(_) => todo!(),
|
||||
BodyNode::ForLoop(_forloop) => return None,
|
||||
BodyNode::IfChain(_chain) => return None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,6 +374,35 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// empty everything except for some comments
|
||||
fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result {
|
||||
let span = brace.span.span();
|
||||
let start = span.start();
|
||||
let end = span.end();
|
||||
|
||||
if start.line == end.line {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writeln!(self.out)?;
|
||||
|
||||
for idx in start.line..end.line {
|
||||
let line = &self.src[idx];
|
||||
if line.trim().starts_with("//") {
|
||||
for _ in 0..self.out.indent + 1 {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
writeln!(self.out, "{}", line.trim()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..self.out.indent {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr_length(expr: &Expr) -> Option<usize> {
|
||||
|
|
|
@ -1,45 +1,23 @@
|
|||
//! pretty printer for rsx!
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
use crate::Buffer;
|
||||
use proc_macro2::Span;
|
||||
|
||||
impl Buffer {
|
||||
pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result {
|
||||
use crate::Writer;
|
||||
|
||||
impl Writer<'_> {
|
||||
pub fn write_raw_expr(&mut self, placement: Span) -> Result {
|
||||
/*
|
||||
We want to normalize the expr to the appropriate indent level.
|
||||
*/
|
||||
|
||||
// in a perfect world, just fire up the rust pretty printer
|
||||
// pretty_print_rust_code_as_if_it_were_rustfmt()
|
||||
|
||||
use syn::spanned::Spanned;
|
||||
let placement = exp.span();
|
||||
let start = placement.start();
|
||||
let end = placement.end();
|
||||
// let num_spaces_desired = (self.indent * 4) as isize;
|
||||
|
||||
// print comments
|
||||
// let mut queued_comments = vec![];
|
||||
// let mut offset = 2;
|
||||
// loop {
|
||||
// let line = &self.src[start.line - offset];
|
||||
// if line.trim_start().starts_with("//") {
|
||||
// queued_comments.push(line);
|
||||
// } else {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// offset += 1;
|
||||
// }
|
||||
// let had_comments = !queued_comments.is_empty();
|
||||
// for comment in queued_comments.into_iter().rev() {
|
||||
// writeln!(self.buf, "{}", comment)?;
|
||||
// }
|
||||
|
||||
// if the expr is on one line, just write it directly
|
||||
if start.line == end.line {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}",
|
||||
&self.src[start.line - 1][start.column - 1..end.column].trim()
|
||||
)?;
|
||||
|
@ -50,7 +28,7 @@ impl Buffer {
|
|||
// This involves unshifting the first line if it's aligned
|
||||
let first_line = &self.src[start.line - 1];
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}",
|
||||
&first_line[start.column - 1..first_line.len()].trim()
|
||||
)?;
|
||||
|
@ -66,7 +44,7 @@ impl Buffer {
|
|||
};
|
||||
|
||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||
writeln!(self.buf)?;
|
||||
writeln!(self.out)?;
|
||||
// trim the leading whitespace
|
||||
let line = match id {
|
||||
x if x == (end.line - start.line) - 1 => &line[..end.column],
|
||||
|
@ -75,52 +53,17 @@ impl Buffer {
|
|||
|
||||
if offset < 0 {
|
||||
for _ in 0..-offset {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
write!(self.buf, "{}", line)?;
|
||||
write!(self.out, "{}", line)?;
|
||||
} else {
|
||||
let offset = offset as usize;
|
||||
let right = &line[offset..];
|
||||
write!(self.buf, "{}", right)?;
|
||||
write!(self.out, "{}", right)?;
|
||||
}
|
||||
}
|
||||
|
||||
// let first = &self.src[start.line - 1];
|
||||
// let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
|
||||
// let offset = num_spaces_real - num_spaces_desired;
|
||||
|
||||
// for (row, line) in self.src[start.line - 1..end.line].iter().enumerate() {
|
||||
// let line = match row {
|
||||
// 0 => &line[start.column - 1..],
|
||||
// a if a == (end.line - start.line) => &line[..end.column - 1],
|
||||
// _ => line,
|
||||
// };
|
||||
|
||||
// writeln!(self.buf)?;
|
||||
// // trim the leading whitespace
|
||||
// if offset < 0 {
|
||||
// for _ in 0..-offset {
|
||||
// write!(self.buf, " ")?;
|
||||
// }
|
||||
|
||||
// write!(self.buf, "{}", line)?;
|
||||
// } else {
|
||||
// let offset = offset as usize;
|
||||
// let right = &line[offset..];
|
||||
// write!(self.buf, "{}", right)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// :(
|
||||
// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String {
|
||||
// let formatted = prettyplease::unparse_expr(exp);
|
||||
// for line in formatted.lines() {
|
||||
// write!(self.buf, "{}", line)?;
|
||||
// self.new_line()?;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use crate::writer::*;
|
||||
use collect_macros::byte_offset;
|
||||
use dioxus_rsx::CallBody;
|
||||
|
||||
use crate::buffer::*;
|
||||
use crate::util::*;
|
||||
use proc_macro2::LineColumn;
|
||||
use syn::{ExprMacro, MacroDelimiter};
|
||||
|
||||
mod buffer;
|
||||
mod collect_macros;
|
||||
mod component;
|
||||
mod element;
|
||||
mod expr;
|
||||
mod util;
|
||||
mod writer;
|
||||
|
||||
/// A modification to the original file to be applied by an IDE
|
||||
///
|
||||
|
@ -40,60 +42,83 @@ pub struct FormattedBlock {
|
|||
/// Nested blocks of RSX will be handled automatically
|
||||
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
||||
let mut formatted_blocks = Vec::new();
|
||||
let mut last_bracket_end = 0;
|
||||
|
||||
use triple_accel::{levenshtein_search, Match};
|
||||
let parsed = syn::parse_file(contents).unwrap();
|
||||
|
||||
for Match { end, start, k } in levenshtein_search(b"rsx! {", contents.as_bytes()) {
|
||||
if k > 1 {
|
||||
let mut macros = vec![];
|
||||
collect_macros::collect_from_file(&parsed, &mut macros);
|
||||
|
||||
// No macros, no work to do
|
||||
if macros.is_empty() {
|
||||
return formatted_blocks;
|
||||
}
|
||||
|
||||
let mut writer = Writer {
|
||||
src: contents.lines().collect::<Vec<_>>(),
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
// Dont parse nested macros
|
||||
let mut end_span = LineColumn { column: 0, line: 0 };
|
||||
for item in macros {
|
||||
let macro_path = &item.path.segments[0].ident;
|
||||
|
||||
// this macro is inside the last macro we parsed, skip it
|
||||
if macro_path.span().start() < end_span {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ensure the marker is not nested
|
||||
if start < last_bracket_end {
|
||||
continue;
|
||||
// item.parse_body::<CallBody>();
|
||||
let body = item.parse_body::<CallBody>().unwrap();
|
||||
|
||||
let rsx_start = macro_path.span().start();
|
||||
|
||||
writer.out.indent = &writer.src[rsx_start.line - 1]
|
||||
.chars()
|
||||
.take_while(|c| *c == ' ')
|
||||
.count()
|
||||
/ 4;
|
||||
|
||||
// Oneliner optimization
|
||||
if writer.is_short_children(&body.roots).is_some() {
|
||||
writer.write_ident(&body.roots[0]).unwrap();
|
||||
} else {
|
||||
writer.write_body_indented(&body.roots).unwrap();
|
||||
}
|
||||
|
||||
let mut indent_level = {
|
||||
// walk backwards from start until we find a new line
|
||||
let mut lines = contents[..start].lines().rev();
|
||||
match lines.next() {
|
||||
Some(line) => {
|
||||
if line.starts_with("//") || line.starts_with("///") {
|
||||
continue;
|
||||
}
|
||||
// writing idents leaves the final line ended at the end of the last ident
|
||||
if writer.out.buf.contains('\n') {
|
||||
writer.out.new_line().unwrap();
|
||||
writer.out.tab().unwrap();
|
||||
}
|
||||
|
||||
line.chars().take_while(|c| *c == ' ').count() / 4
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
let span = match item.delimiter {
|
||||
MacroDelimiter::Paren(b) => b.span,
|
||||
MacroDelimiter::Brace(b) => b.span,
|
||||
MacroDelimiter::Bracket(b) => b.span,
|
||||
};
|
||||
|
||||
let remaining = &contents[end - 1..];
|
||||
let bracket_end = find_bracket_end(remaining).unwrap();
|
||||
let sub_string = &contents[end..bracket_end + end - 1];
|
||||
last_bracket_end = bracket_end + end - 1;
|
||||
let mut formatted = String::new();
|
||||
|
||||
let mut new = fmt_block(sub_string, indent_level).unwrap();
|
||||
std::mem::swap(&mut formatted, &mut writer.out.buf);
|
||||
|
||||
if new.len() <= 80 && !new.contains('\n') {
|
||||
new = format!(" {new} ");
|
||||
let start = byte_offset(contents, span.start()) + 1;
|
||||
let end = byte_offset(contents, span.end()) - 1;
|
||||
|
||||
// if the new string is not multiline, don't try to adjust the marker ending
|
||||
// We want to trim off any indentation that there might be
|
||||
indent_level = 0;
|
||||
if formatted.len() <= 80 && !formatted.contains('\n') {
|
||||
formatted = format!(" {} ", formatted);
|
||||
}
|
||||
|
||||
let end_marker = end + bracket_end - indent_level * 4 - 1;
|
||||
end_span = span.end();
|
||||
|
||||
if new == contents[end..end_marker] {
|
||||
if contents[start..end] == formatted {
|
||||
continue;
|
||||
}
|
||||
|
||||
formatted_blocks.push(FormattedBlock {
|
||||
formatted: new,
|
||||
start: end,
|
||||
end: end_marker,
|
||||
formatted,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -101,10 +126,27 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||
}
|
||||
|
||||
pub fn write_block_out(body: CallBody) -> Option<String> {
|
||||
let mut buf = Buffer {
|
||||
src: vec!["".to_string()],
|
||||
indent: 0,
|
||||
..Buffer::default()
|
||||
let mut buf = Writer {
|
||||
src: vec![""],
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
// Oneliner optimization
|
||||
if buf.is_short_children(&body.roots).is_some() {
|
||||
buf.write_ident(&body.roots[0]).unwrap();
|
||||
} else {
|
||||
buf.write_body_indented(&body.roots).unwrap();
|
||||
}
|
||||
|
||||
buf.consume()
|
||||
}
|
||||
|
||||
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
||||
let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
|
||||
|
||||
let mut buf = Writer {
|
||||
src: raw.lines().collect(),
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
// Oneliner optimization
|
||||
|
@ -118,14 +160,15 @@ pub fn write_block_out(body: CallBody) -> Option<String> {
|
|||
}
|
||||
|
||||
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?;
|
||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
||||
|
||||
let mut buf = Buffer {
|
||||
src: block.lines().map(|f| f.to_string()).collect(),
|
||||
indent: indent_level,
|
||||
..Buffer::default()
|
||||
let mut buf = Writer {
|
||||
src: block.lines().collect(),
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
buf.out.indent = indent_level;
|
||||
|
||||
// Oneliner optimization
|
||||
if buf.is_short_children(&body.roots).is_some() {
|
||||
buf.write_ident(&body.roots[0]).unwrap();
|
||||
|
@ -134,8 +177,8 @@ pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
|||
}
|
||||
|
||||
// writing idents leaves the final line ended at the end of the last ident
|
||||
if buf.buf.contains('\n') {
|
||||
buf.new_line().unwrap();
|
||||
if buf.out.buf.contains('\n') {
|
||||
buf.out.new_line().unwrap();
|
||||
}
|
||||
|
||||
buf.consume()
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
pub fn find_bracket_end(contents: &str) -> Option<usize> {
|
||||
let mut depth = 0;
|
||||
|
||||
for (i, c) in contents.chars().enumerate() {
|
||||
if c == '{' {
|
||||
depth += 1;
|
||||
} else if c == '}' {
|
||||
depth -= 1;
|
||||
|
||||
if depth == 0 {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
215
packages/autofmt/src/writer.rs
Normal file
215
packages/autofmt/src/writer.rs
Normal file
|
@ -0,0 +1,215 @@
|
|||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, ForLoop};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use quote::ToTokens;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, Expr, ExprIf};
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Writer<'a> {
|
||||
pub src: Vec<&'a str>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub comments: VecDeque<usize>,
|
||||
pub out: Buffer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer<'_> {
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el),
|
||||
BodyNode::Component(component) => self.write_component(component),
|
||||
BodyNode::Text(text) => self.out.write_text(text),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp.span()),
|
||||
BodyNode::ForLoop(forloop) => self.write_for_loop(forloop),
|
||||
BodyNode::IfChain(ifchain) => self.write_if_chain(ifchain),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.out.buf)
|
||||
}
|
||||
|
||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||
// collect all comments upwards
|
||||
let start = child.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
|
||||
if line.trim().starts_with("//") || line.is_empty() {
|
||||
if id != 0 {
|
||||
self.comments.push_front(id);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_was_empty = false;
|
||||
while let Some(comment_line) = self.comments.pop_front() {
|
||||
let line = &self.src[comment_line];
|
||||
if line.is_empty() {
|
||||
if !last_was_empty {
|
||||
self.out.new_line()?;
|
||||
}
|
||||
last_was_empty = true;
|
||||
} else {
|
||||
last_was_empty = false;
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "{}", self.src[comment_line].trim())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||
self.out.indent += 1;
|
||||
|
||||
self.write_body_no_indent(children)?;
|
||||
|
||||
self.out.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
let iter = children.iter().peekable().enumerate();
|
||||
|
||||
for (idx, child) in iter {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
_ => break 'line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 6
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 6
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 6
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 6
|
||||
}
|
||||
ElementAttr::EventTokens { tokens, name } => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||
self.cached_formats.entry(location)
|
||||
{
|
||||
let formatted = prettyplease::unparse_expr(tokens);
|
||||
let len = if formatted.contains('\n') {
|
||||
10000
|
||||
} else {
|
||||
formatted.len()
|
||||
};
|
||||
e.insert(formatted);
|
||||
len
|
||||
} else {
|
||||
self.cached_formats[&location].len()
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 6
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
self.cached_formats
|
||||
.entry(Location::new(expr.span().start()))
|
||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
||||
.as_str()
|
||||
}
|
||||
|
||||
fn write_for_loop(&mut self, forloop: &ForLoop) -> std::fmt::Result {
|
||||
write!(
|
||||
self.out,
|
||||
"for {} in {} {{",
|
||||
forloop.pat.clone().into_token_stream(),
|
||||
prettyplease::unparse_expr(&forloop.expr)
|
||||
)?;
|
||||
|
||||
if forloop.body.is_empty() {
|
||||
write!(self.out, "}}")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.write_body_indented(&forloop.body)?;
|
||||
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_if_chain(&mut self, ifchain: &ExprIf) -> std::fmt::Result {
|
||||
self.write_raw_expr(ifchain.span())
|
||||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
fn line_length(&self) -> usize {
|
||||
self.end().line - self.start().line
|
||||
}
|
||||
}
|
|
@ -1,32 +1,41 @@
|
|||
macro_rules! twoway {
|
||||
($val:literal => $name:ident) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let src = include_str!(concat!("./samples/", $val, ".rsx"));
|
||||
let formatted = dioxus_autofmt::fmt_file(src);
|
||||
let out = dioxus_autofmt::apply_formats(src, formatted);
|
||||
pretty_assertions::assert_eq!(&src, &out);
|
||||
}
|
||||
(
|
||||
$(
|
||||
|
||||
// doc attrs
|
||||
$( #[doc = $doc:expr] )*
|
||||
$name:ident
|
||||
),*
|
||||
) => {
|
||||
$(
|
||||
$( #[doc = $doc] )*
|
||||
#[test]
|
||||
fn $name() {
|
||||
let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
|
||||
let formatted = dioxus_autofmt::fmt_file(src);
|
||||
let out = dioxus_autofmt::apply_formats(src, formatted);
|
||||
pretty_assertions::assert_eq!(&src, &out);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
twoway! ("simple" => simple);
|
||||
|
||||
twoway! ("comments" => comments);
|
||||
|
||||
twoway! ("attributes" => attributes);
|
||||
|
||||
twoway! ("manual_props" => manual_props);
|
||||
|
||||
twoway! ("complex" => complex);
|
||||
|
||||
twoway! ("tiny" => tiny);
|
||||
|
||||
twoway! ("tinynoopt" => tinynoopt);
|
||||
|
||||
twoway! ("long" => long);
|
||||
|
||||
twoway! ("key" => key);
|
||||
|
||||
// Disabled because we can't handle comments on exprs yet
|
||||
twoway! ("multirsx" => multirsx);
|
||||
twoway![
|
||||
simple,
|
||||
comments,
|
||||
attributes,
|
||||
manual_props,
|
||||
complex,
|
||||
tiny,
|
||||
tinynoopt,
|
||||
long,
|
||||
key,
|
||||
multirsx,
|
||||
commentshard,
|
||||
emoji,
|
||||
messy_indent,
|
||||
long_exprs,
|
||||
ifchain_forloop,
|
||||
t2,
|
||||
reallylong
|
||||
];
|
||||
|
|
|
@ -45,6 +45,15 @@ rsx! {
|
|||
a: "123"
|
||||
}
|
||||
|
||||
// Short attributes
|
||||
div { a: "123", a: "123", a: "123", a: "123", a: "123", a: "123", a: "123", a: "123", a: "123" }
|
||||
div {
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123",
|
||||
a: "123"
|
||||
}
|
||||
}
|
||||
|
|
42
packages/autofmt/tests/samples/commentshard.rsx
Normal file
42
packages/autofmt/tests/samples/commentshard.rsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
rsx! {
|
||||
// Comments
|
||||
div {
|
||||
// Comments
|
||||
class: "asdasd",
|
||||
|
||||
// Comments
|
||||
"hello world"
|
||||
|
||||
// Comments
|
||||
expr1,
|
||||
|
||||
// Comments
|
||||
expr2,
|
||||
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
expr3,
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
}
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
// todo some work in here
|
||||
//
|
||||
// todo some work in here
|
||||
}
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
class: "hello world",
|
||||
|
||||
// todo some work in here
|
||||
class: "hello world"
|
||||
}
|
||||
}
|
||||
}
|
5
packages/autofmt/tests/samples/emoji.rsx
Normal file
5
packages/autofmt/tests/samples/emoji.rsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
rsx! {
|
||||
div { class: "asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd",
|
||||
section { "🦀🦀🦀🦀🦀🦀🦀🦀🦀🦀" }
|
||||
}
|
||||
}
|
13
packages/autofmt/tests/samples/ifchain_forloop.rsx
Normal file
13
packages/autofmt/tests/samples/ifchain_forloop.rsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
rsx! {
|
||||
// Does this work?
|
||||
for i in b {
|
||||
// Hey it works?
|
||||
div {}
|
||||
}
|
||||
|
||||
// Some ifchain
|
||||
if a > 10 {
|
||||
//
|
||||
rsx! { div {} }
|
||||
}
|
||||
}
|
|
@ -33,6 +33,9 @@ pub fn Explainer<'a>(
|
|||
}
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||
left,
|
||||
right
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
17
packages/autofmt/tests/samples/long_exprs.rsx
Normal file
17
packages/autofmt/tests/samples/long_exprs.rsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
rsx! {
|
||||
div {
|
||||
div {
|
||||
div {
|
||||
div {
|
||||
section { class: "body-font overflow-hidden dark:bg-ideblack",
|
||||
div { class: "container px-6 mx-auto",
|
||||
div { class: "-my-8 divide-y-2 divide-gray-100",
|
||||
POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
packages/autofmt/tests/samples/messy_indent.rsx
Normal file
13
packages/autofmt/tests/samples/messy_indent.rsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
fn SaveClipboard(cx: Scope) -> Element {
|
||||
rsx! {
|
||||
div { class: "relative w-1/2 {align} max-w-md leading-8",
|
||||
h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
|
||||
"{title}"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
div { "hello world", "hello world", "hello world" }
|
||||
})
|
||||
}
|
15
packages/autofmt/tests/samples/reallylong.rsx
Normal file
15
packages/autofmt/tests/samples/reallylong.rsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
pub static Icon3: Component<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
svg {
|
||||
class: "w-6 h-6",
|
||||
stroke_linecap: "round",
|
||||
fill: "none",
|
||||
stroke_linejoin: "round",
|
||||
stroke_width: "2",
|
||||
stroke: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
path { d: "M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" }
|
||||
circle { cx: "12", cy: "7", r: "4" }
|
||||
}
|
||||
})
|
||||
};
|
|
@ -37,7 +37,11 @@ rsx! {
|
|||
|
||||
// One level compression
|
||||
div {
|
||||
a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", "Send invitation" }
|
||||
a {
|
||||
class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white",
|
||||
href: "#",
|
||||
"Send invitation"
|
||||
}
|
||||
}
|
||||
|
||||
// Components
|
||||
|
|
7
packages/autofmt/tests/samples/t2.rsx
Normal file
7
packages/autofmt/tests/samples/t2.rsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
rsx! {
|
||||
div {}
|
||||
div {
|
||||
// div {
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,10 @@
|
|||
rsx! { div {} }
|
||||
fn ItWorks() {
|
||||
rsx! {
|
||||
div {
|
||||
div {
|
||||
div {}
|
||||
div {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||
})
|
||||
fn ItWroks() {
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||
left,
|
||||
right
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||
})
|
||||
fn ItWroks() {
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus-core-macro) |
|
||||
[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus_core_macro) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
`dioxus-core-macro` provides a handful of helpful macros used by the `dioxus` crate. These include:
|
||||
|
||||
- The `rsx!` macro that underpins templates and node creation
|
||||
- The `inline_props` that transforms function arguments into an auto-derived struct
|
||||
- The `inline_props` macro transforms function arguments into an auto-derived struct
|
||||
- The `format_args_f` macro which allows f-string formatting with support for expressions
|
||||
|
||||
|
||||
|
@ -44,5 +44,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
3
packages/core/.vscode/settings.json
vendored
Normal file
3
packages/core/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.checkOnSave.allTargets": false
|
||||
}
|
|
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -41,6 +41,7 @@ serde = { version = "1", features = ["derive"], optional = true }
|
|||
tokio = { version = "1", features = ["full"] }
|
||||
dioxus = { path = "../dioxus" }
|
||||
pretty_assertions = "1.3.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -13,7 +13,7 @@ dioxus-core is a fast and featureful VirtualDom implementation written in and fo
|
|||
- Error boundaries through the `anyhow` crate
|
||||
- Customizable memoization
|
||||
|
||||
If just starting out, check out the Guides first.
|
||||
If you are just starting, check out the Guides first.
|
||||
|
||||
# General Theory
|
||||
|
||||
|
@ -21,7 +21,7 @@ The dioxus-core `VirtualDom` object is built around the concept of a `Template`.
|
|||
|
||||
Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves.
|
||||
|
||||
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object.
|
||||
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object and calculates the differences between the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and the new layout, Dioxus will write modifications to the `Mutations` object.
|
||||
|
||||
Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups.
|
||||
|
||||
|
@ -33,37 +33,48 @@ The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler s
|
|||
|
||||
First, start with your app:
|
||||
|
||||
```rust, ignore
|
||||
```rust
|
||||
# use dioxus::core::Mutations;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
// First, declare a root component
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!( div { "hello world" } ))
|
||||
cx.render(rsx!{
|
||||
div { "hello world" }
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Next, create a new VirtualDom using this app as the root component.
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
// The initial render of the dom will generate a stream of edits for the real dom to apply
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
// Somehow, you can apply these edits to the real dom
|
||||
apply_edits_to_real_dom(mutations);
|
||||
}
|
||||
|
||||
# fn apply_edits_to_real_dom(mutations: Mutations) {}
|
||||
```
|
||||
|
||||
Then, we'll want to create a new VirtualDom using this app as the root component.
|
||||
|
||||
```rust, ignore
|
||||
let mut dom = VirtualDom::new(app);
|
||||
```
|
||||
|
||||
To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]:
|
||||
|
||||
```rust, ignore
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
apply_edits_to_real_dom(mutations);
|
||||
```
|
||||
|
||||
We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead:
|
||||
```rust
|
||||
# #![allow(unused)]
|
||||
# use dioxus::prelude::*;
|
||||
|
||||
```rust, ignore
|
||||
# use std::time::Duration;
|
||||
# async fn wait(mut dom: VirtualDom) {
|
||||
// Wait for the dom to be marked dirty internally
|
||||
dom.wait_for_work().await;
|
||||
|
||||
// Or wait for a deadline and then collect edits
|
||||
dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
|
||||
let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
|
||||
# }
|
||||
```
|
||||
|
||||
If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
|
||||
If an event occurs from outside the VirtualDom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
|
||||
|
||||
```rust, ignore
|
||||
loop {
|
||||
|
@ -88,18 +99,18 @@ Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus
|
|||
- React: hooks, concurrency, suspense
|
||||
- Dodrio: bump allocation, double buffering, and some diffing architecture
|
||||
|
||||
Dioxus-core leverages some really cool techniques and hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features:
|
||||
Dioxus-core hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features:
|
||||
|
||||
- managed lifetimes for borrowed data
|
||||
- placeholder approach for suspended vnodes
|
||||
- fiber/interruptible diffing algorithm
|
||||
- custom memory allocator for vnodes and all text content
|
||||
- custom memory allocator for VNodes and all text content
|
||||
- support for fragments w/ lazy normalization
|
||||
- slab allocator for scopes
|
||||
- mirrored-slab approach for remote vdoms
|
||||
- mirrored-slab approach for remote VirtualDoms
|
||||
- dedicated subtrees for rendering into separate contexts from the same app
|
||||
|
||||
There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, it is possible that zero allocations will need to be performed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
|
||||
There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, no allocations may be needed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
|
||||
|
||||
All in all, Dioxus treats memory as a valuable resource. Combined with the memory-efficient footprint of Wasm compilation, Dioxus apps can scale to thousands of components and still stay snappy.
|
||||
|
||||
|
@ -112,5 +123,5 @@ The final implementation of Dioxus must:
|
|||
- Be concurrent. Components should be able to pause rendering to let the screen paint the next frame.
|
||||
- Be disconnected from a specific renderer (no WebSys dependency in the core crate).
|
||||
- Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server.
|
||||
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
|
||||
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
|
||||
- Be "live". Components should be able to be both server-rendered and client rendered without needing frontend APIs.
|
||||
- Be modular. Components and hooks should work anywhere without worrying about the target platform.
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::ptr::NonNull;
|
||||
|
||||
use crate::{
|
||||
nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
|
||||
ScopeId,
|
||||
innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom,
|
||||
AttributeValue, DynamicNode, ScopeId,
|
||||
};
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
|
||||
|
@ -17,7 +19,7 @@ pub(crate) struct ElementRef {
|
|||
pub path: ElementPath,
|
||||
|
||||
// The actual template
|
||||
pub template: *const VNode<'static>,
|
||||
pub template: Option<NonNull<VNode<'static>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -27,9 +29,9 @@ pub enum ElementPath {
|
|||
}
|
||||
|
||||
impl ElementRef {
|
||||
pub(crate) fn null() -> Self {
|
||||
pub(crate) fn none() -> Self {
|
||||
Self {
|
||||
template: std::ptr::null_mut(),
|
||||
template: None,
|
||||
path: ElementPath::Root(0),
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +46,21 @@ impl VirtualDom {
|
|||
self.next_reference(template, ElementPath::Root(path))
|
||||
}
|
||||
|
||||
pub(crate) fn next_null(&mut self) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
let id = entry.key();
|
||||
|
||||
entry.insert(ElementRef::none());
|
||||
ElementId(id)
|
||||
}
|
||||
|
||||
fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
let id = entry.key();
|
||||
|
||||
entry.insert(ElementRef {
|
||||
template: template as *const _ as *mut _,
|
||||
// We know this is non-null because it comes from a reference
|
||||
template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
|
||||
path,
|
||||
});
|
||||
ElementId(id)
|
||||
|
@ -78,6 +89,11 @@ impl VirtualDom {
|
|||
|
||||
// Drop a scope and all its children
|
||||
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
||||
self.dirty_scopes.remove(&DirtyScope {
|
||||
height: self.scopes[id.0].height,
|
||||
id,
|
||||
});
|
||||
|
||||
self.ensure_drop_safety(id);
|
||||
|
||||
if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
|
||||
|
@ -100,6 +116,11 @@ impl VirtualDom {
|
|||
for hook in scope.hook_list.get_mut().drain(..) {
|
||||
drop(unsafe { BumpBox::from_raw(hook) });
|
||||
}
|
||||
|
||||
// Drop all the futures once the hooks are dropped
|
||||
for task_id in scope.spawned_tasks.borrow_mut().drain() {
|
||||
scope.tasks.remove(task_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_scope_inner(&mut self, node: &VNode) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::nodes::RenderReturn;
|
||||
use bumpalo::Bump;
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
|
||||
pub(crate) struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub bump: UnsafeCell<Bump>,
|
||||
pub node: Cell<*const RenderReturn<'static>>,
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ impl BumpFrame {
|
|||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
let bump = Bump::with_capacity(capacity);
|
||||
Self {
|
||||
bump,
|
||||
bump: UnsafeCell::new(bump),
|
||||
node: Cell::new(std::ptr::null()),
|
||||
}
|
||||
}
|
||||
|
@ -26,4 +26,13 @@ impl BumpFrame {
|
|||
|
||||
unsafe { std::mem::transmute(&*node) }
|
||||
}
|
||||
|
||||
pub(crate) fn bump(&self) -> &Bump {
|
||||
unsafe { &*self.bump.get() }
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
|
||||
unsafe { &mut *self.bump.get() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ impl<'b> VirtualDom {
|
|||
// Just move over the placeholder
|
||||
(Aborted(l), Aborted(r)) => r.id.set(l.id.get()),
|
||||
|
||||
// Becomes async, do nothing while we ait
|
||||
// Becomes async, do nothing while we wait
|
||||
(Ready(_nodes), Pending(_fut)) => self.diff_ok_to_async(_nodes, scope),
|
||||
|
||||
// Placeholder becomes something
|
||||
|
@ -65,8 +65,26 @@ impl<'b> VirtualDom {
|
|||
//
|
||||
}
|
||||
|
||||
fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _p: &'b VPlaceholder) {
|
||||
todo!()
|
||||
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
|
||||
let id = self.next_null();
|
||||
p.id.set(Some(id));
|
||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||
|
||||
let pre_edits = self.mutations.edits.len();
|
||||
|
||||
self.remove_node(l, true);
|
||||
|
||||
// We should always have a remove mutation
|
||||
// Eventually we don't want to generate placeholders, so this might not be true. But it's true today
|
||||
assert!(self.mutations.edits.len() > pre_edits);
|
||||
|
||||
// We want to optimize the replace case to use one less mutation if possible
|
||||
// Since mutations are done in reverse, the last node removed will be the first in the stack
|
||||
// Instead of *just* removing it, we can use the replace mutation
|
||||
match self.mutations.edits.pop().unwrap() {
|
||||
Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m: 1 }),
|
||||
_ => panic!("Expected remove mutation from remove_node"),
|
||||
};
|
||||
}
|
||||
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
|
@ -898,34 +916,25 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
fn remove_component_node(&mut self, comp: &VComponent, gen_muts: bool) {
|
||||
// Remove the component reference from the vcomponent so they're not tied together
|
||||
let scope = comp
|
||||
.scope
|
||||
.take()
|
||||
.expect("VComponents to always have a scope");
|
||||
|
||||
// Remove the component from the dom
|
||||
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
|
||||
RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
|
||||
RenderReturn::Aborted(t) => {
|
||||
if let Some(id) = t.id.get() {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
// Restore the props back to the vcomponent in case it gets rendered again
|
||||
let props = self.scopes[scope.0].props.take();
|
||||
|
||||
self.dirty_scopes.remove(&DirtyScope {
|
||||
height: self.scopes[scope.0].height,
|
||||
id: scope,
|
||||
});
|
||||
|
||||
*comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
|
||||
|
||||
// make sure to wipe any of its props and listeners
|
||||
self.ensure_drop_safety(scope);
|
||||
self.scopes.remove(scope.0);
|
||||
// Now drop all the resouces
|
||||
self.drop_scope(scope);
|
||||
}
|
||||
|
||||
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
|
||||
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use smallbox::{smallbox, space::S16, SmallBox};
|
||||
|
||||
use crate::{innerlude::VNode, ScopeState};
|
||||
|
|
|
@ -31,7 +31,8 @@ impl VirtualDom {
|
|||
// If the task completes...
|
||||
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||
let scope = &self.scopes[task.scope.0];
|
||||
scope.spawned_tasks.borrow_mut().remove(&id);
|
||||
|
||||
// Remove it from the scheduler
|
||||
tasks.try_remove(id.0);
|
||||
|
@ -63,10 +64,10 @@ impl VirtualDom {
|
|||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
let arena = scope.current_frame();
|
||||
|
||||
let ret = arena.bump.alloc(match new_nodes {
|
||||
let ret = arena.bump().alloc(match new_nodes {
|
||||
Some(new) => RenderReturn::Ready(new),
|
||||
None => RenderReturn::default(),
|
||||
});
|
||||
|
|
|
@ -7,7 +7,6 @@ use crate::{
|
|||
scopes::{ScopeId, ScopeState},
|
||||
virtual_dom::VirtualDom,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use futures_util::FutureExt;
|
||||
use std::{
|
||||
mem,
|
||||
|
@ -48,11 +47,10 @@ impl VirtualDom {
|
|||
}))
|
||||
}
|
||||
|
||||
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
|
||||
self.scope_stack
|
||||
.last()
|
||||
.copied()
|
||||
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
|
||||
fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
|
||||
let id = self.scope_stack.last().copied()?;
|
||||
let scope = self.scopes.get(id.0)?;
|
||||
Some(scope.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||
|
@ -62,17 +60,10 @@ impl VirtualDom {
|
|||
self.ensure_drop_safety(scope_id);
|
||||
|
||||
let mut new_nodes = unsafe {
|
||||
let scope = self.scopes[scope_id.0].as_mut();
|
||||
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
|
||||
|
||||
// if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
|
||||
if scope.previous_frame().bump.allocated_bytes() == 0 {
|
||||
scope.previous_frame_mut().bump =
|
||||
Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
|
||||
} else {
|
||||
scope.previous_frame_mut().bump.reset();
|
||||
}
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
|
||||
// Make sure to reset the hook counter so we give out hooks in the right order
|
||||
scope.hook_idx.set(0);
|
||||
|
||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||
|
@ -142,7 +133,7 @@ impl VirtualDom {
|
|||
let frame = scope.previous_frame();
|
||||
|
||||
// set the new head of the bump frame
|
||||
let allocated = &*frame.bump.alloc(new_nodes);
|
||||
let allocated = &*frame.bump().alloc(new_nodes);
|
||||
frame.node.set(allocated);
|
||||
|
||||
// And move the render generation forward by one
|
||||
|
|
|
@ -73,7 +73,7 @@ pub struct ScopeState {
|
|||
pub(crate) node_arena_1: BumpFrame,
|
||||
pub(crate) node_arena_2: BumpFrame,
|
||||
|
||||
pub(crate) parent: Option<*mut ScopeState>,
|
||||
pub(crate) parent: Option<*const ScopeState>,
|
||||
pub(crate) id: ScopeId,
|
||||
|
||||
pub(crate) height: u32,
|
||||
|
@ -85,7 +85,7 @@ pub struct ScopeState {
|
|||
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
|
||||
|
||||
pub(crate) tasks: Rc<Scheduler>,
|
||||
pub(crate) spawned_tasks: FxHashSet<TaskId>,
|
||||
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
|
||||
|
||||
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
||||
|
@ -111,14 +111,6 @@ impl<'src> ScopeState {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
match self.render_cnt.get() % 2 {
|
||||
1 => &mut self.node_arena_1,
|
||||
0 => &mut self.node_arena_2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of this component
|
||||
pub fn name(&self) -> &str {
|
||||
self.name
|
||||
|
@ -139,7 +131,7 @@ impl<'src> ScopeState {
|
|||
/// If you need to allocate items that need to be dropped, use bumpalo's box.
|
||||
pub fn bump(&self) -> &Bump {
|
||||
// note that this is actually the previous frame since we use that as scratch space while the component is rendering
|
||||
&self.previous_frame().bump
|
||||
self.previous_frame().bump()
|
||||
}
|
||||
|
||||
/// Get a handle to the currently active head node arena for this Scope
|
||||
|
@ -327,7 +319,9 @@ impl<'src> ScopeState {
|
|||
|
||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||
self.tasks.spawn(self.id, fut)
|
||||
let id = self.tasks.spawn(self.id, fut);
|
||||
self.spawned_tasks.borrow_mut().insert(id);
|
||||
id
|
||||
}
|
||||
|
||||
/// Spawns the future but does not return the [`TaskId`]
|
||||
|
@ -348,6 +342,8 @@ impl<'src> ScopeState {
|
|||
.unbounded_send(SchedulerMsg::TaskNotified(id))
|
||||
.expect("Scheduler should exist");
|
||||
|
||||
self.spawned_tasks.borrow_mut().insert(id);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,9 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
|
|||
///
|
||||
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
///
|
||||
/// #[derive(Props, PartialEq)]
|
||||
/// struct AppProps {
|
||||
/// title: String
|
||||
|
@ -39,7 +41,17 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
|
|||
///
|
||||
/// Components may be composed to make complex apps.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # #![allow(unused)]
|
||||
/// # use dioxus::prelude::*;
|
||||
///
|
||||
/// # #[derive(Props, PartialEq)]
|
||||
/// # struct AppProps {
|
||||
/// # title: String
|
||||
/// # }
|
||||
///
|
||||
/// static ROUTES: &str = "";
|
||||
///
|
||||
/// fn App(cx: Scope<AppProps>) -> Element {
|
||||
/// cx.render(rsx!(
|
||||
/// NavBar { routes: ROUTES }
|
||||
|
@ -47,12 +59,33 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
|
|||
/// Footer {}
|
||||
/// ))
|
||||
/// }
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn NavBar(cx: Scope, routes: &'static str) -> Element {
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Routes: {routes}" }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// fn Footer(cx: Scope) -> Element {
|
||||
/// cx.render(rsx! { div { "Footer" } })
|
||||
/// }
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
/// cx.render(rsx! {
|
||||
/// div { id: "title", children }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
|
||||
/// draw the UI.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # fn App(cx: Scope) -> Element { cx.render(rsx! { div {} }) }
|
||||
///
|
||||
/// let mut vdom = VirtualDom::new(App);
|
||||
/// let edits = vdom.rebuild();
|
||||
/// ```
|
||||
|
@ -249,7 +282,7 @@ impl VirtualDom {
|
|||
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
|
||||
|
||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||
dom.elements.insert(ElementRef::null());
|
||||
dom.elements.insert(ElementRef::none());
|
||||
|
||||
dom
|
||||
}
|
||||
|
@ -354,16 +387,17 @@ impl VirtualDom {
|
|||
// Loop through each dynamic attribute in this template before moving up to the template's parent.
|
||||
while let Some(el_ref) = parent_path {
|
||||
// safety: we maintain references of all vnodes in the element slab
|
||||
let template = unsafe { &*el_ref.template };
|
||||
let template = unsafe { el_ref.template.unwrap().as_ref() };
|
||||
let node_template = template.template.get();
|
||||
let target_path = el_ref.path;
|
||||
|
||||
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||
let this_path = node_template.attr_paths[idx];
|
||||
|
||||
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
||||
// we should fix this so that we look for "onclick" instead of "click"
|
||||
if &attr.name[2..] == name && target_path.is_ascendant(&this_path) {
|
||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||
if attr.name.trim_start_matches("on") == name
|
||||
&& target_path.is_ascendant(&this_path)
|
||||
{
|
||||
listeners.push(&attr.value);
|
||||
|
||||
// Break if the event doesn't bubble anyways
|
||||
|
@ -549,7 +583,7 @@ impl VirtualDom {
|
|||
loop {
|
||||
// first, unload any complete suspense trees
|
||||
for finished_fiber in self.finished_fibers.drain(..) {
|
||||
let scope = &mut self.scopes[finished_fiber.0];
|
||||
let scope = &self.scopes[finished_fiber.0];
|
||||
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
|
||||
|
||||
self.mutations
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use bumpalo::Bump;
|
||||
use dioxus::core::{ElementId, Mutation::*};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::{AttributeValue, BorrowedAttributeValue};
|
||||
use dioxus_core::BorrowedAttributeValue;
|
||||
|
||||
#[test]
|
||||
fn attrs_cycle() {
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use futures_util::Future;
|
||||
|
||||
#[test]
|
||||
fn catches_panic() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let a = dom.rebuild();
|
||||
|
||||
dbg!(a);
|
||||
_ = dom.rebuild();
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
|
@ -16,43 +14,21 @@ fn app(cx: Scope) -> Element {
|
|||
h1 { "Title" }
|
||||
|
||||
NoneChild {}
|
||||
ThrowChild {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn NoneChild(cx: Scope) -> Element {
|
||||
fn NoneChild(_cx: Scope) -> Element {
|
||||
None
|
||||
}
|
||||
|
||||
fn PanicChild(cx: Scope) -> Element {
|
||||
panic!("Rendering panicked for whatever reason");
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "It works!" }
|
||||
})
|
||||
}
|
||||
|
||||
fn ThrowChild(cx: Scope) -> Element {
|
||||
cx.throw(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd"))?;
|
||||
|
||||
let g: i32 = "123123".parse().throw(cx)?;
|
||||
let _g: i32 = "123123".parse().throw(cx)?;
|
||||
|
||||
cx.render(rsx! {
|
||||
div {}
|
||||
})
|
||||
}
|
||||
|
||||
fn custom_allocator(cx: Scope) -> Element {
|
||||
let g = String::new();
|
||||
|
||||
let p = g.as_str();
|
||||
|
||||
let g2 = cx.use_hook(|| 123);
|
||||
// cx.spawn(async move {
|
||||
|
||||
// //
|
||||
// // println!("Thig is {p}");
|
||||
// });
|
||||
|
||||
None
|
||||
}
|
||||
|
|
305
packages/core/tests/fuzzing.rs
Normal file
305
packages/core/tests/fuzzing.rs
Normal file
|
@ -0,0 +1,305 @@
|
|||
use dioxus::prelude::Props;
|
||||
use dioxus_core::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
fn random_ns() -> Option<&'static str> {
|
||||
let namespace = rand::random::<u8>() % 2;
|
||||
match namespace {
|
||||
0 => None,
|
||||
1 => Some(Box::leak(
|
||||
format!("ns{}", rand::random::<usize>()).into_boxed_str(),
|
||||
)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> {
|
||||
match rand::random::<u8>() % 2 {
|
||||
0 => TemplateAttribute::Static {
|
||||
name: Box::leak(format!("attr{}", rand::random::<usize>()).into_boxed_str()),
|
||||
value: Box::leak(format!("value{}", rand::random::<usize>()).into_boxed_str()),
|
||||
namespace: random_ns(),
|
||||
},
|
||||
1 => TemplateAttribute::Dynamic {
|
||||
id: {
|
||||
let old_idx = *attr_idx;
|
||||
*attr_idx += 1;
|
||||
old_idx
|
||||
},
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_random_template_node(
|
||||
dynamic_node_types: &mut Vec<DynamicNodeType>,
|
||||
template_idx: &mut usize,
|
||||
attr_idx: &mut usize,
|
||||
depth: usize,
|
||||
) -> TemplateNode<'static> {
|
||||
match rand::random::<u8>() % 4 {
|
||||
0 => {
|
||||
let attrs = {
|
||||
let attrs: Vec<_> = (0..(rand::random::<usize>() % 10))
|
||||
.map(|_| create_random_attribute(attr_idx))
|
||||
.collect();
|
||||
Box::leak(attrs.into_boxed_slice())
|
||||
};
|
||||
TemplateNode::Element {
|
||||
tag: Box::leak(format!("tag{}", rand::random::<usize>()).into_boxed_str()),
|
||||
namespace: random_ns(),
|
||||
attrs,
|
||||
children: {
|
||||
if depth > 4 {
|
||||
&[]
|
||||
} else {
|
||||
let children: Vec<_> = (0..(rand::random::<usize>() % 3))
|
||||
.map(|_| {
|
||||
create_random_template_node(
|
||||
dynamic_node_types,
|
||||
template_idx,
|
||||
attr_idx,
|
||||
depth + 1,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Box::leak(children.into_boxed_slice())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
1 => TemplateNode::Text {
|
||||
text: Box::leak(format!("{}", rand::random::<usize>()).into_boxed_str()),
|
||||
},
|
||||
2 => TemplateNode::DynamicText {
|
||||
id: {
|
||||
let old_idx = *template_idx;
|
||||
*template_idx += 1;
|
||||
dynamic_node_types.push(DynamicNodeType::Text);
|
||||
old_idx
|
||||
},
|
||||
},
|
||||
3 => TemplateNode::Dynamic {
|
||||
id: {
|
||||
let old_idx = *template_idx;
|
||||
*template_idx += 1;
|
||||
dynamic_node_types.push(DynamicNodeType::Other);
|
||||
old_idx
|
||||
},
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_paths(
|
||||
node: &TemplateNode<'static>,
|
||||
current_path: &[u8],
|
||||
node_paths: &mut Vec<Vec<u8>>,
|
||||
attr_paths: &mut Vec<Vec<u8>>,
|
||||
) {
|
||||
match node {
|
||||
TemplateNode::Element { children, attrs, .. } => {
|
||||
for attr in *attrs {
|
||||
match attr {
|
||||
TemplateAttribute::Static { .. } => {}
|
||||
TemplateAttribute::Dynamic { .. } => {
|
||||
attr_paths.push(current_path.to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i, child) in children.iter().enumerate() {
|
||||
let mut current_path = current_path.to_vec();
|
||||
current_path.push(i as u8);
|
||||
generate_paths(child, ¤t_path, node_paths, attr_paths);
|
||||
}
|
||||
}
|
||||
TemplateNode::Text { .. } => {}
|
||||
TemplateNode::DynamicText { .. } => {
|
||||
node_paths.push(current_path.to_vec());
|
||||
}
|
||||
TemplateNode::Dynamic { .. } => {
|
||||
node_paths.push(current_path.to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DynamicNodeType {
|
||||
Text,
|
||||
Other,
|
||||
}
|
||||
|
||||
fn create_random_template(name: &'static str) -> (Template<'static>, Vec<DynamicNodeType>) {
|
||||
let mut dynamic_node_type = Vec::new();
|
||||
let mut template_idx = 0;
|
||||
let mut attr_idx = 0;
|
||||
let roots = (0..(1 + rand::random::<usize>() % 5))
|
||||
.map(|_| {
|
||||
create_random_template_node(&mut dynamic_node_type, &mut template_idx, &mut attr_idx, 0)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(!roots.is_empty());
|
||||
let roots = Box::leak(roots.into_boxed_slice());
|
||||
let mut node_paths = Vec::new();
|
||||
let mut attr_paths = Vec::new();
|
||||
for (i, root) in roots.iter().enumerate() {
|
||||
generate_paths(root, &[i as u8], &mut node_paths, &mut attr_paths);
|
||||
}
|
||||
let node_paths = Box::leak(
|
||||
node_paths
|
||||
.into_iter()
|
||||
.map(|v| &*Box::leak(v.into_boxed_slice()))
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
);
|
||||
let attr_paths = Box::leak(
|
||||
attr_paths
|
||||
.into_iter()
|
||||
.map(|v| &*Box::leak(v.into_boxed_slice()))
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
);
|
||||
(
|
||||
Template { name, roots, node_paths, attr_paths },
|
||||
dynamic_node_type,
|
||||
)
|
||||
}
|
||||
|
||||
fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
|
||||
let range = if depth > 3 { 1 } else { 3 };
|
||||
match rand::random::<u8>() % range {
|
||||
0 => DynamicNode::Placeholder(Default::default()),
|
||||
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| VNode {
|
||||
key: None,
|
||||
parent: Default::default(),
|
||||
template: Cell::new(Template {
|
||||
name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
|
||||
roots: &[TemplateNode::Dynamic { id: 0 }],
|
||||
node_paths: &[&[0]],
|
||||
attr_paths: &[],
|
||||
}),
|
||||
root_ids: Default::default(),
|
||||
dynamic_nodes: cx.bump().alloc([cx.component(
|
||||
create_random_element,
|
||||
DepthProps { depth, root: false },
|
||||
"create_random_element",
|
||||
)]),
|
||||
dynamic_attrs: &[],
|
||||
})),
|
||||
2 => cx.component(
|
||||
create_random_element,
|
||||
DepthProps { depth, root: false },
|
||||
"create_random_element",
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
|
||||
let value = match rand::random::<u8>() % 6 {
|
||||
0 => AttributeValue::Text(Box::leak(
|
||||
format!("{}", rand::random::<usize>()).into_boxed_str(),
|
||||
)),
|
||||
1 => AttributeValue::Float(rand::random()),
|
||||
2 => AttributeValue::Int(rand::random()),
|
||||
3 => AttributeValue::Bool(rand::random()),
|
||||
4 => cx.any_value(rand::random::<usize>()),
|
||||
5 => AttributeValue::None,
|
||||
// Listener(RefCell<Option<ListenerCb<'a>>>),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Attribute {
|
||||
name: Box::leak(format!("attr{}", rand::random::<usize>()).into_boxed_str()),
|
||||
value,
|
||||
namespace: random_ns(),
|
||||
mounted_element: Default::default(),
|
||||
volatile: rand::random(),
|
||||
}
|
||||
}
|
||||
|
||||
static mut TEMPLATE_COUNT: usize = 0;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct DepthProps {
|
||||
depth: usize,
|
||||
root: bool,
|
||||
}
|
||||
|
||||
fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
||||
cx.needs_update();
|
||||
let range = if cx.props.root { 2 } else { 3 };
|
||||
let node = match rand::random::<usize>() % range {
|
||||
0 | 1 => {
|
||||
let (template, dynamic_node_types) = create_random_template(Box::leak(
|
||||
format!(
|
||||
"{}{}",
|
||||
concat!(file!(), ":", line!(), ":", column!(), ":"),
|
||||
{
|
||||
unsafe {
|
||||
let old = TEMPLATE_COUNT;
|
||||
TEMPLATE_COUNT += 1;
|
||||
old
|
||||
}
|
||||
}
|
||||
)
|
||||
.into_boxed_str(),
|
||||
));
|
||||
println!("{template:#?}");
|
||||
let node = VNode {
|
||||
key: None,
|
||||
parent: None,
|
||||
template: Cell::new(template),
|
||||
root_ids: Default::default(),
|
||||
dynamic_nodes: {
|
||||
let dynamic_nodes: Vec<_> = dynamic_node_types
|
||||
.iter()
|
||||
.map(|ty| match ty {
|
||||
DynamicNodeType::Text => DynamicNode::Text(VText {
|
||||
value: Box::leak(
|
||||
format!("{}", rand::random::<usize>()).into_boxed_str(),
|
||||
),
|
||||
id: Default::default(),
|
||||
}),
|
||||
DynamicNodeType::Other => {
|
||||
create_random_dynamic_node(cx, cx.props.depth + 1)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
cx.bump().alloc(dynamic_nodes)
|
||||
},
|
||||
dynamic_attrs: cx.bump().alloc(
|
||||
(0..template.attr_paths.len())
|
||||
.map(|_| create_random_dynamic_attr(cx))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
};
|
||||
Some(node)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
println!("{node:#?}");
|
||||
node
|
||||
}
|
||||
|
||||
// test for panics when creating random nodes and templates
|
||||
#[test]
|
||||
fn create() {
|
||||
for _ in 0..100 {
|
||||
let mut vdom =
|
||||
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
|
||||
let _ = vdom.rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
// test for panics when diffing random nodes
|
||||
// This test will change the template every render which is not very realistic, but it helps stress the system
|
||||
#[test]
|
||||
fn diff() {
|
||||
for _ in 0..10 {
|
||||
let mut vdom =
|
||||
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
|
||||
let _ = vdom.rebuild();
|
||||
for _ in 0..10 {
|
||||
let _ = vdom.render_immediate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -137,213 +137,78 @@ fn free_works_on_root_hooks() {
|
|||
assert_eq!(Rc::strong_count(&ptr), 1);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn old_props_arent_stale() {
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// dbg!("rendering parent");
|
||||
// let cnt = cx.use_hook(|| 0);
|
||||
// *cnt += 1;
|
||||
#[test]
|
||||
fn supports_async() {
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
// if *cnt == 1 {
|
||||
// render!(div { Child { a: "abcdef".to_string() } })
|
||||
// } else {
|
||||
// render!(div { Child { a: "abcdef".to_string() } })
|
||||
// }
|
||||
// }
|
||||
fn app(cx: Scope) -> Element {
|
||||
let colors = use_state(cx, || vec!["green", "blue", "red"]);
|
||||
let padding = use_state(cx, || 10);
|
||||
|
||||
// #[derive(Props, PartialEq)]
|
||||
// struct ChildProps {
|
||||
// a: String,
|
||||
// }
|
||||
// fn Child(cx: Scope<ChildProps>) -> Element {
|
||||
// dbg!("rendering child", &cx.props.a);
|
||||
// render!(div { "child {cx.props.a}" })
|
||||
// }
|
||||
use_effect(cx, colors, |colors| async move {
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
colors.with_mut(|colors| colors.reverse());
|
||||
});
|
||||
|
||||
// let mut dom = new_dom(app, ());
|
||||
// let _ = dom.rebuild();
|
||||
use_effect(cx, padding, |padding| async move {
|
||||
sleep(Duration::from_millis(10)).await;
|
||||
padding.with_mut(|padding| {
|
||||
if *padding < 65 {
|
||||
*padding += 1;
|
||||
} else {
|
||||
*padding = 5;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
let big = colors[0];
|
||||
let mid = colors[1];
|
||||
let small = colors[2];
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
background: "{big}",
|
||||
height: "stretch",
|
||||
width: "stretch",
|
||||
padding: "50",
|
||||
label {
|
||||
"hello",
|
||||
}
|
||||
div {
|
||||
background: "{mid}",
|
||||
height: "auto",
|
||||
width: "stretch",
|
||||
padding: "{padding}",
|
||||
label {
|
||||
"World",
|
||||
}
|
||||
div {
|
||||
background: "{small}",
|
||||
height: "auto",
|
||||
width: "stretch",
|
||||
padding: "20",
|
||||
label {
|
||||
"ddddddd",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// dbg!("forcing update to child");
|
||||
rt.block_on(async {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let _ = dom.rebuild();
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn basic() {
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// render!(div {
|
||||
// Child { a: "abcdef".to_string() }
|
||||
// })
|
||||
// }
|
||||
|
||||
// #[derive(Props, PartialEq)]
|
||||
// struct ChildProps {
|
||||
// a: String,
|
||||
// }
|
||||
|
||||
// fn Child(cx: Scope<ChildProps>) -> Element {
|
||||
// dbg!("rendering child", &cx.props.a);
|
||||
// render!(div { "child {cx.props.a}" })
|
||||
// }
|
||||
|
||||
// let mut dom = new_dom(app, ());
|
||||
// let _ = dom.rebuild();
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn leak_thru_children() {
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// cx.render(rsx! {
|
||||
// Child {
|
||||
// name: "asd".to_string(),
|
||||
// }
|
||||
// });
|
||||
// cx.render(rsx! {
|
||||
// div {}
|
||||
// })
|
||||
// }
|
||||
|
||||
// #[inline_props]
|
||||
// fn Child(cx: Scope, name: String) -> Element {
|
||||
// render!(div { "child {name}" })
|
||||
// }
|
||||
|
||||
// let mut dom = new_dom(app, ());
|
||||
// let _ = dom.rebuild();
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_pass_thru() {
|
||||
// #[inline_props]
|
||||
// fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
// cx.render(rsx! {
|
||||
// header {
|
||||
// nav { children }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// fn NavMenu(cx: Scope) -> Element {
|
||||
// render!( NavBrand {}
|
||||
// div {
|
||||
// NavStart {}
|
||||
// NavEnd {}
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
// fn NavBrand(cx: Scope) -> Element {
|
||||
// render!(div {})
|
||||
// }
|
||||
|
||||
// fn NavStart(cx: Scope) -> Element {
|
||||
// render!(div {})
|
||||
// }
|
||||
|
||||
// fn NavEnd(cx: Scope) -> Element {
|
||||
// render!(div {})
|
||||
// }
|
||||
|
||||
// #[inline_props]
|
||||
// fn MainContainer<'a>(
|
||||
// cx: Scope,
|
||||
// nav: Element<'a>,
|
||||
// body: Element<'a>,
|
||||
// footer: Element<'a>,
|
||||
// ) -> Element {
|
||||
// cx.render(rsx! {
|
||||
// div {
|
||||
// class: "columns is-mobile",
|
||||
// div {
|
||||
// class: "column is-full",
|
||||
// nav,
|
||||
// body,
|
||||
// footer,
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// let nav = cx.render(rsx! {
|
||||
// NavContainer {
|
||||
// NavMenu {}
|
||||
// }
|
||||
// });
|
||||
// let body = cx.render(rsx! {
|
||||
// div {}
|
||||
// });
|
||||
// let footer = cx.render(rsx! {
|
||||
// div {}
|
||||
// });
|
||||
|
||||
// cx.render(rsx! {
|
||||
// MainContainer {
|
||||
// nav: nav,
|
||||
// body: body,
|
||||
// footer: footer,
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// let mut dom = new_dom(app, ());
|
||||
// let _ = dom.rebuild();
|
||||
|
||||
// for _ in 0..40 {
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// }
|
||||
// }
|
||||
for _ in 0..10 {
|
||||
dom.wait_for_work().await;
|
||||
let _edits = dom.render_immediate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,42 +5,48 @@ use std::future::IntoFuture;
|
|||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_works() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
{
|
||||
let mutations = dom.rebuild().santize();
|
||||
|
||||
// We should at least get the top-level template in before pausing for the children
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
// mutations.templates,
|
||||
// [
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Waiting for child..." },
|
||||
// CreateStaticPlaceholder,
|
||||
// AppendChildren { m: 2 },
|
||||
// SaveTemplate { name: "template", m: 1 }
|
||||
// ]
|
||||
// );
|
||||
|
||||
// And we should load it in and assign the placeholder properly
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
|
||||
// can we even?
|
||||
AssignId { path: &[1], id: ElementId(3) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
// wait just a moment, not enough time for the boundary to resolve
|
||||
|
||||
dom.wait_for_work().await;
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
{
|
||||
let mutations = dom.rebuild().santize();
|
||||
|
||||
// We should at least get the top-level template in before pausing for the children
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
// mutations.templates,
|
||||
// [
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Waiting for child..." },
|
||||
// CreateStaticPlaceholder,
|
||||
// AppendChildren { m: 2 },
|
||||
// SaveTemplate { name: "template", m: 1 }
|
||||
// ]
|
||||
// );
|
||||
|
||||
// And we should load it in and assign the placeholder properly
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
|
||||
// can we even?
|
||||
AssignId { path: &[1], id: ElementId(3) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.wait_for_work().await;
|
||||
});
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus-desktop) |
|
||||
[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus_desktop) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -37,10 +37,10 @@ This requires that webview is installed on the target system. WebView is install
|
|||
## Features
|
||||
|
||||
- Simple, one-line launch for desktop apps
|
||||
- Dioxus virtualdom running on a native thread
|
||||
- Dioxus VirtualDom running on a native thread
|
||||
- Full HTML/CSS support via `wry` and `tao`
|
||||
- Exposed `window` and `Proxy` types from tao for direct window manipulation
|
||||
- Helpful hooks for
|
||||
- Helpful hooks for accessing the window, WebView, and running javascript.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -53,5 +53,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -18,10 +18,11 @@ $ cargo new --bin demo
|
|||
$ cd app
|
||||
```
|
||||
|
||||
Add Dioxus with the `desktop` feature:
|
||||
Add Dioxus and the `desktop` renderer feature:
|
||||
|
||||
```shell
|
||||
$ cargo add dioxus --features desktop
|
||||
$ cargo add dioxus
|
||||
$ cargo add dioxus-desktop
|
||||
```
|
||||
|
||||
Edit your `main.rs`:
|
||||
|
|
|
@ -9,7 +9,7 @@ repository = "https://github.com/DioxusLabs/dioxus/"
|
|||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
rust-version = "1.60.0"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
|
|
|
@ -229,7 +229,7 @@ of logic. Hooks provide us a way of retrieving state from the `Scope` and using
|
|||
it to render UI elements.
|
||||
|
||||
By convention, all hooks are functions that should start with `use_`. We can
|
||||
use hooks to define state and modify it from within listeners.
|
||||
use hooks to define the state and modify it from within listeners.
|
||||
|
||||
```rust, ignore
|
||||
fn app(cx: Scope) -> Element {
|
||||
|
@ -249,7 +249,7 @@ In a sense, hooks let us add a field of state to our component without declaring
|
|||
an explicit state struct. However, this means we need to "load" the struct in the right
|
||||
order. If that order is wrong, then the hook will pick the wrong state and panic.
|
||||
|
||||
Most hooks you'll write are simply composition of other hooks:
|
||||
Most hooks you'll write are simply compositions of other hooks:
|
||||
|
||||
```rust, ignore
|
||||
fn use_username(cx: &ScopeState, id: Uuid) -> bool {
|
||||
|
@ -309,7 +309,7 @@ Beyond this overview, Dioxus supports:
|
|||
|
||||
Good luck!
|
||||
|
||||
## Inspiration, Resources, Alternatives and Credits
|
||||
## Inspiration, Resources, Alternatives, and Credits
|
||||
|
||||
Dioxus is inspired by:
|
||||
- React: for its hooks, concurrency, suspense
|
||||
|
@ -318,7 +318,7 @@ Dioxus is inspired by:
|
|||
Alternatives to Dioxus include:
|
||||
- Yew: supports function components and web, but no SSR, borrowed data, or bump allocation. Rather slow at times.
|
||||
- Percy: supports function components, web, ssr, but lacks state management
|
||||
- Sycamore: supports function components, web, ssr, but closer to SolidJS than React
|
||||
- Sycamore: supports function components, web, ssr, but is closer to SolidJS than React
|
||||
- MoonZoom/Seed: opinionated frameworks based on the Elm model (message, update) - no hooks
|
||||
|
||||
We've put a lot of work into making Dioxus ergonomic and *familiar*.
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react", "state-management"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
@ -40,9 +40,9 @@ Inspired by atom-based state management solutions, all state in Fermi starts as
|
|||
static NAME: Atom<&str> = |_| "Dioxus";
|
||||
```
|
||||
|
||||
From anywhere in our app, we can read our the value of our atom:
|
||||
From anywhere in our app, we can read the value of our atom:
|
||||
|
||||
```rust, ignore
|
||||
```rust, ignores
|
||||
fn NameCard(cx: Scope) -> Element {
|
||||
let name = use_read(cx, NAME);
|
||||
cx.render(rsx!{ h1 { "Hello, {name}"} })
|
||||
|
@ -83,9 +83,9 @@ $ cargo run --example fermi
|
|||
|
||||
## Features
|
||||
|
||||
Broadly our feature set to required to be released includes:
|
||||
Broadly our feature set required to be released includes:
|
||||
- [x] Support for Atoms
|
||||
- [x] Support for AtomRef (for values that aren't clone)
|
||||
- [x] Support for AtomRef (for values that aren't `Clone`)
|
||||
- [ ] Support for Atom Families
|
||||
- [ ] Support for memoized Selectors
|
||||
- [ ] Support for memoized SelectorFamilies
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus-hooks) |
|
||||
[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -54,5 +54,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -7,8 +7,8 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir
|
|||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://docs.rs/dioxus"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-html/latest/dioxus-html) |
|
||||
[API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -61,7 +61,7 @@ impl DioxusElement for div {
|
|||
|
||||
All elements should be defined as a zero-sized-struct (also known as unit struct). These structs are zero-cost and just provide the type-level trickery to Rust for compile-time correct templates.
|
||||
|
||||
Attributes would then be implemented as methods on these unit structs.
|
||||
Attributes would then be implemented as constants on these unit structs.
|
||||
|
||||
The HTML namespace is defined mostly with macros. However, the expanded form would look something like this:
|
||||
```rust
|
||||
|
@ -71,14 +71,8 @@ impl DioxusElement for base {
|
|||
const NAME_SPACE: Option<&'static str> = None;
|
||||
}
|
||||
impl base {
|
||||
#[inline]
|
||||
fn href<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> {
|
||||
f.attr("href", v, None, false)
|
||||
}
|
||||
#[inline]
|
||||
fn target<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> {
|
||||
f.attr("target", v, None, false)
|
||||
}
|
||||
const href: (&'static str, Option<'static str>, bool) = ("href", None, false);
|
||||
const target: (&'static str, Option<'static str>, bool) = ("target", None, false);
|
||||
}
|
||||
```
|
||||
Because attributes are defined as methods on the unit struct, they guard the attribute creation behind a compile-time correct interface.
|
||||
|
@ -114,5 +108,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -294,7 +294,7 @@ builder_constructors! {
|
|||
rel: LinkType DEFAULT,
|
||||
sizes: String DEFAULT, // FIXME
|
||||
title: String DEFAULT, // FIXME
|
||||
r#type: Mime DEFAULT,
|
||||
r#type: Mime "type",
|
||||
integrity: String DEFAULT,
|
||||
};
|
||||
|
||||
|
@ -312,7 +312,7 @@ builder_constructors! {
|
|||
/// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
|
||||
/// element.
|
||||
style None {
|
||||
r#type: Mime DEFAULT,
|
||||
r#type: Mime "type",
|
||||
media: String DEFAULT, // FIXME media query
|
||||
nonce: Nonce DEFAULT,
|
||||
title: String DEFAULT, // FIXME
|
||||
|
@ -517,7 +517,7 @@ builder_constructors! {
|
|||
ol None {
|
||||
reversed: Bool DEFAULT,
|
||||
start: isize DEFAULT,
|
||||
r#type: OrderedListType DEFAULT,
|
||||
r#type: OrderedListType "type",
|
||||
};
|
||||
|
||||
/// Build a
|
||||
|
@ -546,7 +546,7 @@ builder_constructors! {
|
|||
href: Uri DEFAULT,
|
||||
hreflang: LanguageTag DEFAULT,
|
||||
target: Target DEFAULT,
|
||||
r#type: Mime DEFAULT,
|
||||
r#type: Mime "type",
|
||||
// ping: SpacedList<Uri>,
|
||||
// rel: SpacedList<LinkType>,
|
||||
ping: SpacedList DEFAULT,
|
||||
|
@ -737,7 +737,7 @@ builder_constructors! {
|
|||
muted: Bool DEFAULT,
|
||||
preload: Preload DEFAULT,
|
||||
src: Uri DEFAULT,
|
||||
r#loop: Bool DEFAULT,
|
||||
r#loop: Bool "loop",
|
||||
};
|
||||
|
||||
/// Build a
|
||||
|
@ -783,7 +783,7 @@ builder_constructors! {
|
|||
controls: Bool DEFAULT,
|
||||
crossorigin: CrossOrigin DEFAULT,
|
||||
height: usize DEFAULT,
|
||||
r#loop: Bool DEFAULT,
|
||||
r#loop: Bool "loop",
|
||||
muted: Bool DEFAULT,
|
||||
preload: Preload DEFAULT,
|
||||
playsinline: Bool DEFAULT,
|
||||
|
@ -801,7 +801,7 @@ builder_constructors! {
|
|||
embed None {
|
||||
height: usize DEFAULT,
|
||||
src: Uri DEFAULT,
|
||||
r#type: Mime DEFAULT,
|
||||
r#type: Mime "type",
|
||||
width: usize DEFAULT,
|
||||
};
|
||||
|
||||
|
@ -819,13 +819,13 @@ builder_constructors! {
|
|||
srcdoc: Uri DEFAULT,
|
||||
width: usize DEFAULT,
|
||||
|
||||
marginWidth: String DEFAULT,
|
||||
margin_width: String "marginWidth",
|
||||
align: String DEFAULT,
|
||||
longdesc: String DEFAULT,
|
||||
|
||||
scrolling: String DEFAULT,
|
||||
marginHeight: String DEFAULT,
|
||||
frameBorder: String DEFAULT,
|
||||
margin_height: String "marginHeight",
|
||||
frame_border: String "frameBorder",
|
||||
// sandbox: SpacedSet<Sandbox>,
|
||||
};
|
||||
|
||||
|
@ -837,7 +837,7 @@ builder_constructors! {
|
|||
form: Id DEFAULT,
|
||||
height: usize DEFAULT,
|
||||
name: Id DEFAULT,
|
||||
r#type: Mime DEFAULT,
|
||||
r#type: Mime "type",
|
||||
typemustmatch: Bool DEFAULT,
|
||||
usemap: String DEFAULT, // TODO should be a fragment starting with '#'
|
||||
width: usize DEFAULT,
|
||||
|
@ -861,7 +861,7 @@ builder_constructors! {
|
|||
/// element.
|
||||
source None {
|
||||
src: Uri DEFAULT,
|
||||
r#type: Mime DEFAULT,
|
||||
r#type: Mime "type",
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1622,22 +1622,22 @@ trait_methods! {
|
|||
ascent: "ascent";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeName>
|
||||
attributeName: "attributeName";
|
||||
attribute_name: "attributeName";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeType>
|
||||
attributeType: "attributeType";
|
||||
attribute_type: "attributeType";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/azimuth>
|
||||
azimuth: "azimuth";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseFrequency>
|
||||
baseFrequency: "baseFrequency";
|
||||
base_frequency: "baseFrequency";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseline-shift>
|
||||
baseline_shift: "baseline-shift";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseProfile>
|
||||
baseProfile: "baseProfile";
|
||||
base_profile: "baseProfile";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/bbox>
|
||||
bbox: "bbox";
|
||||
|
@ -1652,7 +1652,7 @@ trait_methods! {
|
|||
by: "by";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/calcMode>
|
||||
calcMode: "calcMode";
|
||||
calc_mode: "calcMode";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cap-height>
|
||||
cap_height: "cap-height";
|
||||
|
@ -1664,7 +1664,7 @@ trait_methods! {
|
|||
clip: "clip";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clipPathUnits>
|
||||
clipPathUnits: "clipPathUnits";
|
||||
clip_path_units: "clipPathUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-path>
|
||||
clip_path: "clip-path";
|
||||
|
@ -1688,10 +1688,10 @@ trait_methods! {
|
|||
color_rendering: "color-rendering";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/contentScriptType>
|
||||
contentScriptType: "contentScriptType";
|
||||
content_script_type: "contentScriptType";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/contentStyleType>
|
||||
contentStyleType: "contentStyleType";
|
||||
content_style_type: "contentStyleType";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/crossorigin>
|
||||
crossorigin: "crossorigin";
|
||||
|
@ -1715,7 +1715,7 @@ trait_methods! {
|
|||
descent: "descent";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/diffuseConstant>
|
||||
diffuseConstant: "diffuseConstant";
|
||||
diffuse_constant: "diffuseConstant";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/direction>
|
||||
direction: "direction";
|
||||
|
@ -1739,7 +1739,7 @@ trait_methods! {
|
|||
dy: "dy";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/edgeMode>
|
||||
edgeMode: "edgeMode";
|
||||
edge_mode: "edgeMode";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/elevation>
|
||||
elevation: "elevation";
|
||||
|
@ -1829,13 +1829,13 @@ trait_methods! {
|
|||
glyph_orientation_vertical: "glyph-orientation-vertical";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/glyphRef>
|
||||
glyphRef: "glyphRef";
|
||||
glyph_ref: "glyphRef";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform>
|
||||
gradientTransform: "gradientTransform";
|
||||
gradient_transform: "gradientTransform";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits>
|
||||
gradientUnits: "gradientUnits";
|
||||
gradient_units: "gradientUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/hanging>
|
||||
hanging: "hanging";
|
||||
|
@ -1889,28 +1889,28 @@ trait_methods! {
|
|||
k4: "k4";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kernelMatrix>
|
||||
kernelMatrix: "kernelMatrix";
|
||||
kernel_matrix: "kernelMatrix";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kernelUnitLength>
|
||||
kernelUnitLength: "kernelUnitLength";
|
||||
kernel_unit_length: "kernelUnitLength";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning>
|
||||
kerning: "kerning";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyPoints>
|
||||
keyPoints: "keyPoints";
|
||||
key_points: "keyPoints";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keySplines>
|
||||
keySplines: "keySplines";
|
||||
key_splines: "keySplines";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyTimes>
|
||||
keyTimes: "keyTimes";
|
||||
key_times: "keyTimes";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lang>
|
||||
lang: "lang";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust>
|
||||
lengthAdjust: "lengthAdjust";
|
||||
length_adjust: "lengthAdjust";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing>
|
||||
letter_spacing: "letter-spacing";
|
||||
|
@ -1919,7 +1919,7 @@ trait_methods! {
|
|||
lighting_color: "lighting-color";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/limitingConeAngle>
|
||||
limitingConeAngle: "limitingConeAngle";
|
||||
limiting_cone_angle: "limitingConeAngle";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/local>
|
||||
local: "local";
|
||||
|
@ -1930,26 +1930,26 @@ trait_methods! {
|
|||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-mid>
|
||||
marker_mid: "marker-mid";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker_start>
|
||||
marker_start: "marker_start";
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-start>
|
||||
marker_start: "marker-start";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerHeight>
|
||||
markerHeight: "markerHeight";
|
||||
marker_height: "markerHeight";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerUnits>
|
||||
markerUnits: "markerUnits";
|
||||
marker_units: "markerUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerWidth>
|
||||
markerWidth: "markerWidth";
|
||||
marker_width: "markerWidth";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mask>
|
||||
mask: "mask";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskContentUnits>
|
||||
maskContentUnits: "maskContentUnits";
|
||||
mask_content_units: "maskContentUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskUnits>
|
||||
maskUnits: "maskUnits";
|
||||
mask_units: "maskUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mathematical>
|
||||
mathematical: "mathematical";
|
||||
|
@ -1973,7 +1973,7 @@ trait_methods! {
|
|||
name: "name";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/numOctaves>
|
||||
numOctaves: "numOctaves";
|
||||
num_octaves: "numOctaves";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/offset>
|
||||
offset: "offset";
|
||||
|
@ -2015,16 +2015,16 @@ trait_methods! {
|
|||
path: "path";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pathLength>
|
||||
pathLength: "pathLength";
|
||||
path_length: "pathLength";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternContentUnits>
|
||||
patternContentUnits: "patternContentUnits";
|
||||
pattern_content_units: "patternContentUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternTransform>
|
||||
patternTransform: "patternTransform";
|
||||
pattern_transform: "patternTransform";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternUnits>
|
||||
patternUnits: "patternUnits";
|
||||
pattern_units: "patternUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ping>
|
||||
ping: "ping";
|
||||
|
@ -2036,22 +2036,22 @@ trait_methods! {
|
|||
points: "points";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtX>
|
||||
pointsAtX: "pointsAtX";
|
||||
points_at_x: "pointsAtX";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtY>
|
||||
pointsAtY: "pointsAtY";
|
||||
points_at_y: "pointsAtY";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtZ>
|
||||
pointsAtZ: "pointsAtZ";
|
||||
points_at_z: "pointsAtZ";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAlpha>
|
||||
preserveAlpha: "preserveAlpha";
|
||||
preserve_alpha: "preserveAlpha";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio>
|
||||
preserveAspectRatio: "preserveAspectRatio";
|
||||
preserve_aspect_ratio: "preserveAspectRatio";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/primitiveUnits>
|
||||
primitiveUnits: "primitiveUnits";
|
||||
primitive_units: "primitiveUnits";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/r>
|
||||
r: "r";
|
||||
|
@ -2060,13 +2060,13 @@ trait_methods! {
|
|||
radius: "radius";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/referrerPolicy>
|
||||
referrerPolicy: "referrerPolicy";
|
||||
referrer_policy: "referrerPolicy";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refX>
|
||||
refX: "refX";
|
||||
ref_x: "refX";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refY>
|
||||
refY: "refY";
|
||||
ref_y: "refY";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rel>
|
||||
rel: "rel";
|
||||
|
@ -2075,16 +2075,16 @@ trait_methods! {
|
|||
rendering_intent: "rendering-intent";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatCount>
|
||||
repeatCount: "repeatCount";
|
||||
repeat_count: "repeatCount";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatDur>
|
||||
repeatDur: "repeatDur";
|
||||
repeat_dur: "repeatDur";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredExtensions>
|
||||
requiredExtensions: "requiredExtensions";
|
||||
required_extensions: "requiredExtensions";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredFeatures>
|
||||
requiredFeatures: "requiredFeatures";
|
||||
required_features: "requiredFeatures";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/restart>
|
||||
restart: "restart";
|
||||
|
@ -2120,22 +2120,22 @@ trait_methods! {
|
|||
spacing: "spacing";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularConstant>
|
||||
specularConstant: "specularConstant";
|
||||
specular_constant: "specularConstant";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularExponent>
|
||||
specularExponent: "specularExponent";
|
||||
specular_exponent: "specularExponent";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/speed>
|
||||
speed: "speed";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod>
|
||||
spreadMethod: "spreadMethod";
|
||||
spread_method: "spreadMethod";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/startOffset>
|
||||
startOffset: "startOffset";
|
||||
start_offset: "startOffset";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stdDeviation>
|
||||
stdDeviation: "stdDeviation";
|
||||
std_deviation: "stdDeviation";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stemh>
|
||||
stemh: "stemh";
|
||||
|
@ -2144,13 +2144,13 @@ trait_methods! {
|
|||
stemv: "stemv";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stitchTiles>
|
||||
stitchTiles: "stitchTiles";
|
||||
stitch_tiles: "stitchTiles";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop_color>
|
||||
stop_color: "stop_color";
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-color>
|
||||
stop_color: "stop-color";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop_opacity>
|
||||
stop_opacity: "stop_opacity";
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity>
|
||||
stop_opacity: "stop-opacity";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/strikethrough-position>
|
||||
strikethrough_position: "strikethrough-position";
|
||||
|
@ -2189,25 +2189,25 @@ trait_methods! {
|
|||
style: "style";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/surfaceScale>
|
||||
surfaceScale: "surfaceScale";
|
||||
surface_scale: "surfaceScale";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/systemLanguage>
|
||||
systemLanguage: "systemLanguage";
|
||||
system_language: "systemLanguage";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tabindex>
|
||||
tabindex: "tabindex";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tableValues>
|
||||
tableValues: "tableValues";
|
||||
table_values: "tableValues";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/target>
|
||||
target: "target";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/targetX>
|
||||
targetX: "targetX";
|
||||
target_x: "targetX";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/targetY>
|
||||
targetY: "targetY";
|
||||
target_y: "targetY";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor>
|
||||
text_anchor: "text-anchor";
|
||||
|
@ -2219,7 +2219,7 @@ trait_methods! {
|
|||
text_rendering: "text-rendering";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength>
|
||||
textLength: "textLength";
|
||||
text_length: "textLength";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/to>
|
||||
to: "to";
|
||||
|
@ -2342,6 +2342,6 @@ trait_methods! {
|
|||
z: "z";
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/zoomAndPan>
|
||||
zoomAndPan: "zoomAndPan";
|
||||
zoom_and_pan: "zoomAndPan";
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::events::{
|
|||
};
|
||||
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
||||
use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
|
||||
use crate::DragData;
|
||||
use keyboard_types::{Code, Key, Modifiers};
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
|
@ -40,6 +41,7 @@ uncheck_convert![
|
|||
CompositionEvent => CompositionData,
|
||||
KeyboardEvent => KeyboardData,
|
||||
MouseEvent => MouseData,
|
||||
MouseEvent => DragData,
|
||||
TouchEvent => TouchData,
|
||||
PointerEvent => PointerData,
|
||||
WheelEvent => WheelData,
|
||||
|
@ -117,6 +119,14 @@ impl From<&MouseEvent> for MouseData {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&MouseEvent> for DragData {
|
||||
fn from(value: &MouseEvent) -> Self {
|
||||
Self {
|
||||
mouse: MouseData::from(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TouchEvent> for TouchData {
|
||||
fn from(e: &TouchEvent) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus-interpreter-js) |
|
||||
[API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus_interpreter_js) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -42,5 +42,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -144,8 +144,6 @@ class Interpreter {
|
|||
this.nodes[root].textContent = text;
|
||||
}
|
||||
SetAttribute(id, field, value, ns) {
|
||||
console.log("set attribute", id, field, value, ns);
|
||||
|
||||
if (value === null) {
|
||||
this.RemoveAttribute(id, field, ns);
|
||||
}
|
||||
|
@ -228,7 +226,6 @@ class Interpreter {
|
|||
}
|
||||
|
||||
MakeTemplateNode(node) {
|
||||
console.log("making template node", node);
|
||||
switch (node.type) {
|
||||
case "Text":
|
||||
return document.createTextNode(node.text);
|
||||
|
|
|
@ -129,6 +129,34 @@ mod js {
|
|||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
const bool_attrs = {
|
||||
allowfullscreen: true,
|
||||
allowpaymentrequest: true,
|
||||
async: true,
|
||||
autofocus: true,
|
||||
autoplay: true,
|
||||
checked: true,
|
||||
controls: true,
|
||||
default: true,
|
||||
defer: true,
|
||||
disabled: true,
|
||||
formnovalidate: true,
|
||||
hidden: true,
|
||||
ismap: true,
|
||||
itemscope: true,
|
||||
loop: true,
|
||||
multiple: true,
|
||||
muted: true,
|
||||
nomodule: true,
|
||||
novalidate: true,
|
||||
open: true,
|
||||
playsinline: true,
|
||||
readonly: true,
|
||||
required: true,
|
||||
reversed: true,
|
||||
selected: true,
|
||||
truespeed: true,
|
||||
};
|
||||
"#;
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react", "liveview"]
|
||||
description = "Build server-side apps with Dioxus"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
|
@ -13,18 +13,16 @@ license = "MIT/Apache-2.0"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.23.0", features = ["full"] }
|
||||
thiserror = "1.0.38"
|
||||
futures-util = { version = "0.3.25", default-features = false, features = [
|
||||
"sink",
|
||||
] }
|
||||
futures-channel = { version = "0.3.25", features = ["sink"] }
|
||||
pretty_env_logger = "0.4.0"
|
||||
tokio = { version = "1.23.0", features = ["time"] }
|
||||
tokio-stream = { version = "0.1.11", features = ["net"] }
|
||||
|
||||
tokio-util = { version = "0.7.4", features = ["rt"] }
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
tokio-util = { version = "0.7.4", features = ["full"] }
|
||||
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" }
|
||||
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" }
|
||||
|
@ -34,13 +32,9 @@ warp = { version = "0.3.3", optional = true }
|
|||
|
||||
# axum
|
||||
axum = { version = "0.6.1", optional = true, features = ["ws"] }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
|
||||
# salvo
|
||||
salvo = { version = "0.37.7", optional = true, features = ["ws"] }
|
||||
thiserror = "1.0.38"
|
||||
uuid = { version = "1.2.2", features = ["v4"] }
|
||||
anyhow = "1.0.68"
|
||||
|
||||
# actix is ... complicated?
|
||||
# actix-files = { version = "0.6.2", optional = true }
|
||||
|
@ -48,6 +42,7 @@ anyhow = "1.0.68"
|
|||
# actix-ws = { version = "0.2.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_env_logger = { version = "0.4.0" }
|
||||
tokio = { version = "1.23.0", features = ["full"] }
|
||||
dioxus = { path = "../dioxus", version = "0.3.0" }
|
||||
warp = "0.3.3"
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-liveview/latest/dioxus-liveview) |
|
||||
[API Docs](https://docs.rs/dioxus-liveview/latest/dioxus_liveview) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
`dioxus-liveview` provides adapters for running the Dioxus VirtualDom over a websocket connection.
|
||||
`dioxus-liveview` provides adapters for running the Dioxus VirtualDom over a WebSocket connection.
|
||||
|
||||
The current backend frameworks supported include:
|
||||
|
||||
|
@ -34,9 +34,9 @@ The current backend frameworks supported include:
|
|||
- Warp
|
||||
- Salvo
|
||||
|
||||
Dioxus-LiveView exports a number of primitives to wire up an app into an existing backend framework.
|
||||
Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
|
||||
|
||||
- A threadpool for spawning the `!Send` VirtualDom and interacting with it from the websocket
|
||||
- A ThreadPool for spawning the `!Send` VirtualDom and interacting with it from WebSockets
|
||||
- An adapter for transforming various socket types into the `LiveViewSocket` type
|
||||
- The glue to load the interpreter into your app
|
||||
|
||||
|
@ -51,5 +51,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -12,7 +12,13 @@ class IPC {
|
|||
|
||||
let ws = new WebSocket(WS_ADDR);
|
||||
|
||||
function ping() {
|
||||
ws.send("__ping__");
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
// we ping every 30 seconds to keep the websocket alive
|
||||
setInterval(ping, 30000);
|
||||
ws.send(serializeIpcMessage("initialize"));
|
||||
};
|
||||
|
||||
|
@ -21,8 +27,11 @@ class IPC {
|
|||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
let edits = JSON.parse(event.data);
|
||||
window.interpreter.handleEdits(edits);
|
||||
// Ignore pongs
|
||||
if (event.data != "__pong__") {
|
||||
let edits = JSON.parse(event.data);
|
||||
window.interpreter.handleEdits(edits);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws = ws;
|
||||
|
|
|
@ -127,9 +127,13 @@ where
|
|||
_ = vdom.wait_for_work() => {}
|
||||
|
||||
evt = ws.next() => {
|
||||
match evt {
|
||||
match evt.as_ref().map(|o| o.as_deref()) {
|
||||
// respond with a pong every ping to keep the websocket alive
|
||||
Some(Ok("__ping__")) => {
|
||||
ws.send("__pong__".to_string()).await?;
|
||||
}
|
||||
Some(Ok(evt)) => {
|
||||
if let Ok(IpcMessage { params }) = serde_json::from_str::<IpcMessage>(&evt) {
|
||||
if let Ok(IpcMessage { params }) = serde_json::from_str::<IpcMessage>(evt) {
|
||||
vdom.handle_event(¶ms.name, params.data.into_any(), params.element, params.bubbles);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ description = "Mobile-compatible renderer for Dioxus"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
license = "MIT/Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-mobile/latest/dioxus-mobile) |
|
||||
[API Docs](https://docs.rs/dioxus-mobile/latest/dioxus_mobile) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -94,7 +94,7 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
To configure the webview, menubar, and other important desktop-specific features, checkout out some of the launch configuration in the [API reference](https://docs.rs/dioxus-mobile/).
|
||||
To configure the web view, menubar, and other important desktop-specific features, checkout out some of the launch configurations in the [API reference](https://docs.rs/dioxus-mobile/).
|
||||
|
||||
## Future Steps
|
||||
|
||||
|
@ -113,5 +113,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -7,7 +7,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-native-core-macro/latest/dioxus-native-core-macro) |
|
||||
[API Docs](https://docs.rs/dioxus-native-core-macro/latest/dioxus_native_core_macro) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
`dioxus-native-core-macro` provides a handful of macros used by native-core for native renderers like TUI, Blitz, and Freya to derive their own state.
|
||||
`dioxus-native-core-macro` provides a handful of macros used by native-core for native renderers like TUI, Blitz, and Freya to derive their state.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
@ -39,5 +39,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -7,7 +7,7 @@ repository = "https://github.com/DioxusLabs/dioxus/"
|
|||
homepage = "https://dioxuslabs.com"
|
||||
description = "Build natively rendered apps with Dioxus"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
|
@ -30,3 +30,5 @@ lightningcss = "1.0.0-alpha.39"
|
|||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro" }
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
|
|
|
@ -19,15 +19,15 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-native-core/latest/dioxus-native-core) |
|
||||
[API Docs](https://docs.rs/dioxus-native-core/latest/dioxus_native_core) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
`dioxus-native-core` provides a number of helpful utilities for lazily resolving computed values of the Dioxus VirtualDom to be used in conjunction with a native rendering engine.
|
||||
`dioxus-native-core` provides several helpful utilities for lazily resolving computed values of the Dioxus VirtualDom to be used in conjunction with a native rendering engine.
|
||||
|
||||
The main "value-add" of this crate over implementing your own native tree is that this tree is incrementally recomputed using the Dioxus VirtualDom's edit stream. Only parts of the tree that rely on each other will be redrawn - all else will be ignored.
|
||||
The main "value-add" of this crate over implementing your native tree is that this tree is incrementally recomputed using the Dioxus VirtualDom's edit stream. Only parts of the tree that rely on each other will be redrawn - all else will be ignored.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
@ -41,5 +41,5 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
|
|
@ -295,47 +295,31 @@ pub struct DirtyNodes {
|
|||
}
|
||||
|
||||
impl DirtyNodes {
|
||||
pub fn insert(&mut self, depth: u16, node_id: NodeId) {
|
||||
self.map
|
||||
.entry(depth)
|
||||
.or_insert_with(FxHashSet::default)
|
||||
.insert(node_id);
|
||||
}
|
||||
|
||||
fn pop_front(&mut self) -> Option<NodeId> {
|
||||
let (&depth, values) = self.map.iter_mut().next()?;
|
||||
let key = *values.iter().next()?;
|
||||
let node_id = values.take(&key)?;
|
||||
if values.is_empty() {
|
||||
self.map.remove(&depth);
|
||||
fn add_node(&mut self, node_id: NodeId) {
|
||||
let node_id = node_id.0;
|
||||
let index = node_id / 64;
|
||||
let bit = node_id % 64;
|
||||
let encoded = 1 << bit;
|
||||
if let Some(passes) = self.passes_dirty.get_mut(index) {
|
||||
*passes |= encoded;
|
||||
} else {
|
||||
self.passes_dirty.resize(index + 1, 0);
|
||||
self.passes_dirty[index] |= encoded;
|
||||
}
|
||||
Some(node_id)
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) -> Option<NodeId> {
|
||||
let (&depth, values) = self.map.iter_mut().rev().next()?;
|
||||
let key = *values.iter().next()?;
|
||||
let node_id = values.take(&key)?;
|
||||
if values.is_empty() {
|
||||
self.map.remove(&depth);
|
||||
}
|
||||
Some(node_id)
|
||||
fn is_empty(&self) -> bool {
|
||||
self.passes_dirty.iter().all(|dirty| *dirty == 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dirty_nodes() {
|
||||
let mut dirty_nodes = DirtyNodes::default();
|
||||
|
||||
dirty_nodes.insert(1, NodeId(1));
|
||||
dirty_nodes.insert(0, NodeId(0));
|
||||
dirty_nodes.insert(2, NodeId(3));
|
||||
dirty_nodes.insert(1, NodeId(2));
|
||||
|
||||
assert_eq!(dirty_nodes.pop_front(), Some(NodeId(0)));
|
||||
assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2))));
|
||||
assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2))));
|
||||
assert_eq!(dirty_nodes.pop_front(), Some(NodeId(3)));
|
||||
fn pop(&mut self) -> Option<NodeId> {
|
||||
let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?;
|
||||
let passes = self.passes_dirty[index];
|
||||
let node_id = passes.trailing_zeros();
|
||||
let encoded = 1 << node_id;
|
||||
self.passes_dirty[index] &= !encoded;
|
||||
Some(NodeId((index * 64) + node_id as usize))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -362,6 +346,29 @@ impl DirtyNodeStates {
|
|||
dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id);
|
||||
}
|
||||
}
|
||||
if values.is_empty() {
|
||||
self.dirty.remove(&height);
|
||||
}
|
||||
|
||||
Some((height, node_id))
|
||||
}
|
||||
|
||||
fn pop_back(&mut self, pass_id: PassId) -> Option<(u16, NodeId)> {
|
||||
let (&height, values) = self
|
||||
.dirty
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|(_, values)| values.contains_key(&pass_id))?;
|
||||
let dirty = values.get_mut(&pass_id)?;
|
||||
let node_id = dirty.pop()?;
|
||||
if dirty.is_empty() {
|
||||
values.remove(&pass_id);
|
||||
}
|
||||
if values.is_empty() {
|
||||
self.dirty.remove(&height);
|
||||
}
|
||||
|
||||
Some((height, node_id))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
140
packages/native-core/tests/miri_native.rs
Normal file
140
packages/native-core/tests/miri_native.rs
Normal file
|
@ -0,0 +1,140 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::{
|
||||
node_ref::{AttributeMask, NodeView},
|
||||
real_dom::RealDom,
|
||||
state::{ParentDepState, State},
|
||||
NodeMask, SendAnyMap,
|
||||
};
|
||||
use dioxus_native_core_macro::{sorted_str_slice, State};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct BlablaState {}
|
||||
|
||||
/// Font style are inherited by default if not specified otherwise by some of the supported attributes.
|
||||
impl ParentDepState for BlablaState {
|
||||
type Ctx = ();
|
||||
type DepState = (Self,);
|
||||
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",])));
|
||||
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_node: NodeView,
|
||||
_parent: Option<(&'a Self,)>,
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, State, Default, Debug)]
|
||||
pub struct NodeState {
|
||||
#[parent_dep_state(blabla)]
|
||||
blabla: BlablaState,
|
||||
}
|
||||
|
||||
mod dioxus_elements {
|
||||
macro_rules! builder_constructors {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident {
|
||||
$(
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident,
|
||||
)*
|
||||
};
|
||||
)*
|
||||
) => {
|
||||
$(
|
||||
#[allow(non_camel_case_types)]
|
||||
$(#[$attr])*
|
||||
pub struct $name;
|
||||
|
||||
impl $name {
|
||||
pub const TAG_NAME: &'static str = stringify!($name);
|
||||
pub const NAME_SPACE: Option<&'static str> = None;
|
||||
|
||||
$(
|
||||
pub const $fil: (&'static str, Option<&'static str>, bool) = (stringify!($fil), None, false);
|
||||
)*
|
||||
}
|
||||
|
||||
impl GlobalAttributes for $name {}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GlobalAttributes {}
|
||||
|
||||
pub trait SvgAttributes {}
|
||||
|
||||
builder_constructors! {
|
||||
blabla {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_core_is_okay() {
|
||||
use std::time::Duration;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let colors = use_state(cx, || vec!["green", "blue", "red"]);
|
||||
let padding = use_state(cx, || 10);
|
||||
|
||||
use_effect(cx, colors, |colors| async move {
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
colors.with_mut(|colors| colors.reverse());
|
||||
});
|
||||
|
||||
use_effect(cx, padding, |padding| async move {
|
||||
sleep(Duration::from_millis(10)).await;
|
||||
padding.with_mut(|padding| {
|
||||
if *padding < 65 {
|
||||
*padding += 1;
|
||||
} else {
|
||||
*padding = 5;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let _big = colors[0];
|
||||
let _mid = colors[1];
|
||||
let _small = colors[2];
|
||||
|
||||
cx.render(rsx! {
|
||||
blabla {}
|
||||
})
|
||||
}
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let rdom = Arc::new(Mutex::new(RealDom::<NodeState>::new()));
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let muts = dom.rebuild();
|
||||
let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(muts);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.lock().unwrap().update_state(to_update, ctx);
|
||||
|
||||
for _ in 0..10 {
|
||||
dom.wait_for_work().await;
|
||||
|
||||
let mutations = dom.render_immediate();
|
||||
let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(mutations);
|
||||
|
||||
let ctx = SendAnyMap::new();
|
||||
rdom.lock().unwrap().update_state(to_update, ctx);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-router/latest/dioxus-router) |
|
||||
[API Docs](https://docs.rs/dioxus-router/latest/dioxus_router) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
Dioxus Router is a first-party Router for all your Dioxus Apps. It provides a React-Router style interface using somewhat loose typing rules.
|
||||
Dioxus Router is a first-party Router for all your Dioxus Apps. It provides a React-Router-style interface using somewhat loose typing rules.
|
||||
|
||||
```rust, ignore
|
||||
fn app() {
|
||||
|
@ -52,6 +52,6 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||
attributes,
|
||||
_is_static: false,
|
||||
key: None,
|
||||
brace: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -107,6 +108,7 @@ pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {
|
|||
fields: vec![],
|
||||
children: vec![],
|
||||
manual_props: None,
|
||||
brace: Default::default(),
|
||||
});
|
||||
|
||||
std::mem::swap(child, &mut new_comp);
|
||||
|
|
|
@ -6,8 +6,8 @@ license = "MIT/Apache-2.0"
|
|||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://docs.rs/dioxus-rsx"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/dioxus-rsx/latest/dioxus-rsx) |
|
||||
[API Docs](https://docs.rs/dioxus-rsx/latest/dioxus_rsx) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
|
@ -38,6 +38,6 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@ use syn::{
|
|||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
spanned::Spanned,
|
||||
token, AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result,
|
||||
Token,
|
||||
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
|
@ -30,6 +29,7 @@ pub struct Component {
|
|||
pub fields: Vec<ComponentField>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub manual_props: Option<Expr>,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Component {
|
||||
|
@ -88,13 +88,9 @@ impl Parse for Component {
|
|||
|
||||
// if we see a `{` then we have a block
|
||||
// else parse as a function-like call
|
||||
if stream.peek(token::Brace) {
|
||||
syn::braced!(content in stream);
|
||||
} else {
|
||||
syn::parenthesized!(content in stream);
|
||||
}
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut body = Vec::new();
|
||||
let mut fields = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
|
@ -104,7 +100,7 @@ impl Parse for Component {
|
|||
content.parse::<Token![..]>()?;
|
||||
manual_props = Some(content.parse::<Expr>()?);
|
||||
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
body.push(content.parse::<ComponentField>()?);
|
||||
fields.push(content.parse::<ComponentField>()?);
|
||||
} else {
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
}
|
||||
|
@ -117,9 +113,10 @@ impl Parse for Component {
|
|||
Ok(Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
fields: body,
|
||||
fields,
|
||||
children,
|
||||
manual_props,
|
||||
brace,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ pub struct Element {
|
|||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub _is_static: bool,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
|
@ -25,7 +26,7 @@ impl Parse for Element {
|
|||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in stream);
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||
let mut children: Vec<BodyNode> = vec![];
|
||||
|
@ -152,6 +153,7 @@ impl Parse for Element {
|
|||
name: el_name,
|
||||
attributes,
|
||||
children,
|
||||
brace,
|
||||
_is_static: false,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -95,25 +95,7 @@ impl Parse for BodyNode {
|
|||
|
||||
// Transform for loops into into_iter calls
|
||||
if stream.peek(Token![for]) {
|
||||
let _f = stream.parse::<Token![for]>()?;
|
||||
let pat = stream.parse::<Pat>()?;
|
||||
let _i = stream.parse::<Token![in]>()?;
|
||||
let expr = stream.parse::<Box<Expr>>()?;
|
||||
|
||||
let body;
|
||||
braced!(body in stream);
|
||||
let mut children = vec![];
|
||||
while !body.is_empty() {
|
||||
children.push(body.parse()?);
|
||||
}
|
||||
|
||||
return Ok(BodyNode::ForLoop(ForLoop {
|
||||
for_token: _f,
|
||||
pat,
|
||||
in_token: _i,
|
||||
expr,
|
||||
body: children,
|
||||
}));
|
||||
return Ok(BodyNode::ForLoop(stream.parse()?));
|
||||
}
|
||||
|
||||
// Transform unterminated if statements into terminated optional if statements
|
||||
|
@ -221,6 +203,36 @@ pub struct ForLoop {
|
|||
pub in_token: Token![in],
|
||||
pub expr: Box<Expr>,
|
||||
pub body: Vec<BodyNode>,
|
||||
pub brace_token: token::Brace,
|
||||
}
|
||||
|
||||
impl Parse for ForLoop {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let for_token: Token![for] = input.parse()?;
|
||||
|
||||
let pat = input.parse()?;
|
||||
|
||||
let in_token: Token![in] = input.parse()?;
|
||||
let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
|
||||
let mut children = vec![];
|
||||
|
||||
while !content.is_empty() {
|
||||
children.push(content.parse()?);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
for_token,
|
||||
pat,
|
||||
in_token,
|
||||
body: children,
|
||||
expr: Box::new(expr),
|
||||
brace_token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_if_chain_terminated(chain: &ExprIf) -> bool {
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
|
|||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
keywords = ["dom", "ui", "gui", "react", "ssr"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ This crate is a part of the broader Dioxus ecosystem. For more resources about D
|
|||
|
||||
## Overview
|
||||
|
||||
Dioxus SSR provides utilities to render Dioxus components to valid HTML. Once rendered, the HTML can be rehydrated client side or served from your web-server of choice.
|
||||
Dioxus SSR provides utilities to render Dioxus components to valid HTML. Once rendered, the HTML can be rehydrated client-side or served from your web server of choice.
|
||||
|
||||
```rust, ignore
|
||||
let app: Component = |cx| cx.render(rsx!(div {"hello world!"}));
|
||||
|
@ -23,17 +23,17 @@ let app: Component = |cx| cx.render(rsx!(div {"hello world!"}));
|
|||
let mut vdom = VirtualDom::new(app);
|
||||
let _ = vdom.rebuild();
|
||||
|
||||
let text = dioxus_ssr::render_vdom(&vdom);
|
||||
let text = dioxus_ssr::render(&vdom);
|
||||
assert_eq!(text, "<div>hello world!</div>")
|
||||
```
|
||||
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The simplest example is to simply render some `rsx!` nodes to html. This can be done with the [`render_lazy`] api.
|
||||
The simplest example is to simply render some `rsx!` nodes to HTML. This can be done with the [`render_lazy`] API.
|
||||
|
||||
```rust, ignore
|
||||
let content = dioxus_ssr::render(rsx!{
|
||||
let content = dioxus_ssr::render_lazy(rsx!{
|
||||
div {
|
||||
(0..5).map(|i| rsx!(
|
||||
"Number: {i}"
|
||||
|
@ -45,10 +45,10 @@ let content = dioxus_ssr::render(rsx!{
|
|||
## Rendering a VirtualDom
|
||||
|
||||
```rust, ignore
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let _ = dom.rebuild();
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let _ = vdom.rebuild();
|
||||
|
||||
let content = dioxus_ssr::render_vdom(&dom);
|
||||
let content = dioxus_ssr::render(&vdom);
|
||||
```
|
||||
|
||||
|
||||
|
@ -57,37 +57,42 @@ let content = dioxus_ssr::render_vdom(&dom);
|
|||
|
||||
## Usage in pre-rendering
|
||||
|
||||
This crate is particularly useful in pre-generating pages server-side and then selectively loading dioxus client-side to pick up the reactive elements.
|
||||
This crate is particularly useful in pre-generating pages server-side and then selectively loading Dioxus client-side to pick up the reactive elements.
|
||||
|
||||
In fact, this crate supports hydration out of the box. However, it is extremely important that both the client and server will generate the exact same VirtualDOMs - the client picks up its VirtualDOM assuming that the pre-rendered page output is the same. To do this, you need to make sure that your VirtualDOM implementation is deterministic! This could involve either serializing our app state and sending it to the client, hydrating only parts of the page, or building tests to ensure what's rendered on the server is the same as the client.
|
||||
This crate supports hydration out of the box. However, both the client and server must generate the *exact* same VirtualDOMs - the client picks up its VirtualDOM assuming that the pre-rendered page output is the same. To do this, you need to make sure that your VirtualDOM implementation is deterministic! This could involve either serializing our app state and sending it to the client, hydrating only parts of the page, or building tests to ensure what's rendered on the server is the same as the client.
|
||||
|
||||
With pre-rendering enabled, this crate will generate element nodes with Element IDs pre-associated. During hydration, the Dioxus-WebSys renderer will attach the Virtual nodes to these real nodes after a page query.
|
||||
|
||||
To enable pre-rendering, simply configure the `SsrConfig` with pre-rendering enabled.
|
||||
To enable pre-rendering, simply set the pre-rendering flag to true.
|
||||
|
||||
```rust, ignore
|
||||
let dom = VirtualDom::new(App);
|
||||
let mut vdom = VirtualDom::new(App);
|
||||
|
||||
let text = dioxus_ssr::render_vdom(App, Config { pre_render: true, ..Default::default() });
|
||||
let _ = vdom.rebuild();
|
||||
|
||||
let mut renderer = dioxus_ssr::Renderer::new();
|
||||
renderer.pre_render = true;
|
||||
|
||||
let text = renderer.render(&vdom);
|
||||
```
|
||||
|
||||
## Usage in server-side rendering
|
||||
|
||||
Dioxus SSR can also be to render on the server. Obviously, you can just render the VirtualDOM to a string and send that down.
|
||||
Dioxus SSR can also be used to render on the server. You can just render the VirtualDOM to a string and send that to the client.
|
||||
|
||||
```rust, ignore
|
||||
let text = dioxus_ssr::render_vdom(&vdom);
|
||||
let text = dioxus_ssr::render(&vdom);
|
||||
assert_eq!(text, "<div>hello world!</div>")
|
||||
```
|
||||
|
||||
The rest of the space - IE doing this more efficiently, caching the virtualdom, etc, will all need to be a custom implementation for now.
|
||||
The rest of the space - IE doing this more efficiently, caching the VirtualDom, etc, will all need to be a custom implementation for now.
|
||||
|
||||
## Usage without a VirtualDom
|
||||
|
||||
Dioxus SSR needs an arena to allocate from - whether it be the VirtualDom or a dedicated Bump allocator. To render `rsx!` directly to a string, you'll want to create an `SsrRenderer` and call `render_lazy`.
|
||||
Dioxus SSR needs an arena to allocate from - whether it be the VirtualDom or a dedicated Bump allocator. To render `rsx!` directly to a string, you'll want to create a `Renderer` and call `render_lazy`.
|
||||
|
||||
```rust, ignore
|
||||
let text = dioxus_ssr::SsrRenderer::new().render_lazy(rsx!{
|
||||
let text = dioxus_ssr::Renderer::new().render_lazy(rsx!{
|
||||
div { "hello world" }
|
||||
});
|
||||
assert_eq!(text, "<div>hello world!</div>")
|
||||
|
@ -104,4 +109,4 @@ let text = render_lazy!(rsx!( div { "hello world" } ));
|
|||
Dioxus SSR is a powerful tool to generate static sites. Using Dioxus for static site generation _is_ a bit overkill, however. The new documentation generation library, Doxie, is essentially Dioxus SSR on steroids designed for static site generation with client-side hydration.
|
||||
|
||||
|
||||
Again, simply render the VirtualDOM to a string using `render_vdom` or any of the other render methods.
|
||||
Again, simply render the VirtualDOM to a string using `render` or any of the other render methods.
|
||||
|
|
|
@ -61,9 +61,9 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
## Background
|
||||
|
||||
You can use Html-like semantics with stylesheets, inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
|
||||
You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
|
||||
|
||||
Rink is basically a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
|
||||
Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
|
||||
|
||||
## Limitations
|
||||
|
||||
|
@ -82,13 +82,14 @@ Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready.
|
|||
## Features
|
||||
|
||||
Rink features:
|
||||
- [x] Flexbox based layout system
|
||||
- [x] Flexbox-based layout system
|
||||
- [ ] CSS selectors
|
||||
- [x] inline CSS support
|
||||
- [x] Built-in focusing system
|
||||
- [ ] high-quality keyboard support
|
||||
* [ ] Support for events, hooks, and callbacks<sup>1</sup>
|
||||
* [ ] Html tags<sup>2</sup>
|
||||
* [x] Widgets<sup>1</sup>
|
||||
* [ ] Support for events, hooks, and callbacks<sup>2</sup>
|
||||
* [ ] Html tags<sup>3</sup>
|
||||
|
||||
<sup>1</sup> Basic keyboard and mouse events are implemented.
|
||||
<sup>2</sup> Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality.
|
||||
<sup>1</sup> Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes.
|
||||
<sup>2</sup> Basic keyboard, mouse, and focus events are implemented.
|
||||
<sup>3</sup> Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` *element* won't mean anything nor does it have any additional functionality.
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/rsx-rosetta.svg
|
||||
[crates-url]: https://crates.io/crates/rsx-rosetta
|
||||
[crates-badge]: https://img.shields.io/crates/v/dioxus-web.svg
|
||||
[crates-url]: https://crates.io/crates/dioxus-web
|
||||
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
||||
|
@ -19,15 +19,15 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/guide/) |
|
||||
[API Docs](https://docs.rs/rsx-rosetta/latest/rsx-rosetta) |
|
||||
[API Docs](https://docs.rs/dioxus-web/latest/dioxus_web) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
## Overview
|
||||
|
||||
Run Dioxus in the browser using WebAssembly.
|
||||
|
||||
- Relies on sledgehammer and websys to modify the dom
|
||||
- Supports instant hotreloading via the Dioxus CLI
|
||||
- Relies on [sledgehammer-bindgen](https://github.com/Demonthos/sledgehammer_bindgen) and [web-sys](https://github.com/rustwasm/wasm-bindgen/tree/main/crates/web-sys) to modify the dom
|
||||
- Supports instant hot reloading via the Dioxus CLI
|
||||
- Around 60k gzipped
|
||||
|
||||
## Contributing
|
||||
|
@ -41,6 +41,6 @@ This project is licensed under the [MIT license].
|
|||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
|
||||
|
|
|
@ -229,11 +229,16 @@ pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) -
|
|||
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => read_input_to_data(target),
|
||||
|
||||
"click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
|
||||
| "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
|
||||
| "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
"click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
Rc::new(MouseData::from(event))
|
||||
}
|
||||
"drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
|
||||
| "drop" => {
|
||||
let mouse = MouseData::from(event);
|
||||
Rc::new(DragData { mouse })
|
||||
}
|
||||
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
Rc::new(PointerData::from(event))
|
||||
|
|
Loading…
Reference in a new issue