Merge branch 'master' into add-file-data-drag-event
This commit is contained in:
commit
847801908f
575 files changed
+12880
-17751
No files matched your search
+31
-24
@@ -1,64 +1,71 @@
|
||||
[package]
|
||||
name = "dioxus-html"
|
||||
version = "0.3.1"
|
||||
version = { workspace = true }
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-rsx = { workspace = true, optional = true }
|
||||
dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_repr = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
euclid = "0.22.7"
|
||||
enumset = "1.0.11"
|
||||
keyboard-types = "0.6.2"
|
||||
keyboard-types = "0.7"
|
||||
async-trait = "0.1.58"
|
||||
serde-value = "0.7.0"
|
||||
tokio = { workspace = true, features = ["fs", "io-util"], optional = true }
|
||||
rfd = { version = "0.11.3", optional = true }
|
||||
async-channel = "1.8.0"
|
||||
serde_json = { version = "1", optional = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
optional = true
|
||||
version = "0.3.56"
|
||||
features = [
|
||||
"TouchEvent",
|
||||
"MouseEvent",
|
||||
"InputEvent",
|
||||
"ClipboardEvent",
|
||||
"KeyboardEvent",
|
||||
"TouchEvent",
|
||||
"WheelEvent",
|
||||
"AnimationEvent",
|
||||
"TransitionEvent",
|
||||
"PointerEvent",
|
||||
"FocusEvent",
|
||||
"CompositionEvent",
|
||||
"ClipboardEvent",
|
||||
"DragEvent",
|
||||
"Element",
|
||||
"DomRect",
|
||||
"ScrollIntoViewOptions",
|
||||
"ScrollLogicalPosition",
|
||||
"ScrollBehavior",
|
||||
"TouchEvent",
|
||||
"MouseEvent",
|
||||
"InputEvent",
|
||||
"ClipboardEvent",
|
||||
"DragEvent",
|
||||
"KeyboardEvent",
|
||||
"TouchEvent",
|
||||
"WheelEvent",
|
||||
"AnimationEvent",
|
||||
"TransitionEvent",
|
||||
"PointerEvent",
|
||||
"FocusEvent",
|
||||
"CompositionEvent",
|
||||
"ClipboardEvent",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
|
||||
[features]
|
||||
default = ["serialize"]
|
||||
default = ["serialize", "mounted"]
|
||||
serialize = [
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"serde_json",
|
||||
"euclid/serde",
|
||||
"keyboard-types/serde",
|
||||
"dioxus-core/serialize",
|
||||
]
|
||||
mounted = [
|
||||
"web-sys/Element",
|
||||
"web-sys/DomRect",
|
||||
"web-sys/ScrollIntoViewOptions",
|
||||
"web-sys/ScrollLogicalPosition",
|
||||
"web-sys/ScrollBehavior",
|
||||
"web-sys/HtmlElement",
|
||||
]
|
||||
wasm-bind = ["web-sys", "wasm-bindgen"]
|
||||
native-bind = ["tokio"]
|
||||
hot-reload-context = ["dioxus-rsx"]
|
||||
+4
-11
@@ -1,6 +1,5 @@
|
||||
# `dioxus-html`: Html (and SVG) Namespace for Dioxus
|
||||
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
@@ -8,22 +7,18 @@
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/dioxus-html.svg
|
||||
[crates-url]: https://crates.io/crates/dioxus-html
|
||||
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
||||
|
||||
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
|
||||
[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster
|
||||
|
||||
[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/XgGxMSkvUM
|
||||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) |
|
||||
[Guides](https://dioxuslabs.com/learn/0.4/) |
|
||||
[API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
The Dioxus `rsx!` and `html!` macros can accept any compile-time correct namespace on top of NodeFactory. This crate provides the HTML (and SVG) namespaces which get imported in the Dioxus prelude.
|
||||
@@ -64,6 +59,7 @@ All elements should be defined as a zero-sized-struct (also known as unit struct
|
||||
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
|
||||
struct base;
|
||||
impl DioxusElement for base {
|
||||
@@ -75,8 +71,8 @@ impl base {
|
||||
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.
|
||||
|
||||
Because attributes are defined as methods on the unit struct, they guard the attribute creation behind a compile-time correct interface.
|
||||
|
||||
## How to extend it:
|
||||
|
||||
@@ -93,16 +89,13 @@ mod dioxus_elements {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
||||
- Join the discord and ask questions!
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
||||
@@ -332,7 +332,7 @@ builder_constructors! {
|
||||
meta None {
|
||||
charset: String DEFAULT, // FIXME IANA standard names
|
||||
content: String DEFAULT,
|
||||
http_equiv: HTTPEquiv DEFAULT,
|
||||
http_equiv: String "http-equiv",
|
||||
name: Metadata DEFAULT,
|
||||
};
|
||||
|
||||
@@ -1258,6 +1258,8 @@ builder_constructors! {
|
||||
spellcheck: BoolOrDefault DEFAULT,
|
||||
wrap: Wrap DEFAULT,
|
||||
value: String volatile,
|
||||
|
||||
initial_value: String DEFAULT,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#![allow(clippy::await_holding_refcell_ref)]
|
||||
|
||||
use async_trait::async_trait;
|
||||
use dioxus_core::ScopeState;
|
||||
use std::future::{Future, IntoFuture};
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A struct that implements EvalProvider is sent through [`ScopeState`]'s provide_context function
|
||||
/// so that [`use_eval`] can provide a platform agnostic interface for evaluating JavaScript code.
|
||||
pub trait EvalProvider {
|
||||
fn new_evaluator(&self, js: String) -> Result<Rc<dyn Evaluator>, EvalError>;
|
||||
}
|
||||
|
||||
/// The platform's evaluator.
|
||||
#[async_trait(?Send)]
|
||||
pub trait Evaluator {
|
||||
/// Sends a message to the evaluated JavaScript.
|
||||
fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;
|
||||
/// Receive any queued messages from the evaluated JavaScript.
|
||||
async fn recv(&self) -> Result<serde_json::Value, EvalError>;
|
||||
/// Gets the return value of the JavaScript
|
||||
async fn join(&self) -> Result<serde_json::Value, EvalError>;
|
||||
}
|
||||
|
||||
type EvalCreator = Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
|
||||
|
||||
/// Get a struct that can execute any JavaScript.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Please be very careful with this function. A script with too many dynamic
|
||||
/// parts is practically asking for a hacker to find an XSS vulnerability in
|
||||
/// it. **This applies especially to web targets, where the JavaScript context
|
||||
/// has access to most, if not all of your application data.**
|
||||
pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
|
||||
&*cx.use_hook(|| {
|
||||
let eval_provider = cx
|
||||
.consume_context::<Rc<dyn EvalProvider>>()
|
||||
.expect("evaluator not provided");
|
||||
|
||||
Rc::new(move |script: &str| {
|
||||
eval_provider
|
||||
.new_evaluator(script.to_string())
|
||||
.map(UseEval::new)
|
||||
}) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
|
||||
})
|
||||
}
|
||||
|
||||
/// A wrapper around the target platform's evaluator.
|
||||
#[derive(Clone)]
|
||||
pub struct UseEval {
|
||||
evaluator: Rc<dyn Evaluator + 'static>,
|
||||
}
|
||||
|
||||
impl UseEval {
|
||||
/// Creates a new UseEval
|
||||
pub fn new(evaluator: Rc<dyn Evaluator + 'static>) -> Self {
|
||||
Self { evaluator }
|
||||
}
|
||||
|
||||
/// Sends a [`serde_json::Value`] to the evaluated JavaScript.
|
||||
pub fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
|
||||
self.evaluator.send(data)
|
||||
}
|
||||
|
||||
/// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
|
||||
pub async fn recv(&self) -> Result<serde_json::Value, EvalError> {
|
||||
self.evaluator.recv().await
|
||||
}
|
||||
|
||||
/// Gets the return value of the evaluated JavaScript.
|
||||
pub async fn join(self) -> Result<serde_json::Value, EvalError> {
|
||||
self.evaluator.join().await
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for UseEval {
|
||||
type Output = Result<serde_json::Value, EvalError>;
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(self.join())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error when evaluating JavaScript
|
||||
#[derive(Debug)]
|
||||
pub enum EvalError {
|
||||
/// The provided JavaScript has already been ran.
|
||||
Finished,
|
||||
/// The provided JavaScript is not valid and can't be ran.
|
||||
InvalidJs(String),
|
||||
/// Represents an error communicating between JavaScript and Rust.
|
||||
Communication(String),
|
||||
}
|
||||
@@ -10,15 +10,14 @@ macro_rules! impl_event {
|
||||
$( #[$attr] )*
|
||||
#[inline]
|
||||
pub fn $name<'a, E: crate::EventReturn<T>, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::Attribute<'a> {
|
||||
::dioxus_core::Attribute {
|
||||
name: stringify!($name),
|
||||
value: _cx.listener(move |e: ::dioxus_core::Event<$data>| {
|
||||
::dioxus_core::Attribute::new(
|
||||
stringify!($name),
|
||||
_cx.listener(move |e: ::dioxus_core::Event<$data>| {
|
||||
_f(e).spawn(_cx);
|
||||
}),
|
||||
namespace: None,
|
||||
mounted_element: Default::default(),
|
||||
volatile: false,
|
||||
}
|
||||
None,
|
||||
false,
|
||||
)
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
use std::{any::Any, collections::HashMap, fmt::Debug};
|
||||
|
||||
use dioxus_core::Event;
|
||||
|
||||
@@ -45,6 +45,12 @@ impl FileEngine for SerializedFileEngine {
|
||||
.await
|
||||
.map(|bytes| String::from_utf8_lossy(&bytes).to_string())
|
||||
}
|
||||
|
||||
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
|
||||
self.read_file(file)
|
||||
.await
|
||||
.map(|val| Box::new(val) as Box<dyn Any>)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
@@ -89,6 +95,9 @@ pub trait FileEngine {
|
||||
|
||||
// read a file to string
|
||||
async fn read_file_to_string(&self, file: &str) -> Option<String>;
|
||||
|
||||
// returns a file in platform's native representation
|
||||
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>>;
|
||||
}
|
||||
|
||||
impl_event! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Handles quering data from the renderer
|
||||
//! Handles querying data from the renderer
|
||||
|
||||
use euclid::Rect;
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ impl_event! {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, ignore
|
||||
/// rsx!( button { "click me", onclick: move |_| log::info!("Clicked!`") } )
|
||||
/// rsx!( button { "click me", onclick: move |_| tracing::info!("Clicked!`") } )
|
||||
/// ```
|
||||
///
|
||||
/// ## Reference
|
||||
|
||||
@@ -37,6 +37,9 @@ pub use events::*;
|
||||
pub use global_attributes::*;
|
||||
pub use render_template::*;
|
||||
|
||||
mod eval;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::eval::*;
|
||||
pub use crate::events::*;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::FileEngine;
|
||||
@@ -40,4 +41,9 @@ impl FileEngine for NativeFileEngine {
|
||||
|
||||
Some(contents)
|
||||
}
|
||||
|
||||
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
|
||||
let file = File::open(file).await.ok()?;
|
||||
Some(Box::new(file))
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,14 @@ 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, MountedData, MountedError, MountedResult, RenderedElementBacking, ScrollBehavior,
|
||||
};
|
||||
use crate::{DragData, MountedData};
|
||||
use keyboard_types::{Code, Key, Modifiers};
|
||||
use std::convert::TryInto;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{
|
||||
AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent,
|
||||
ScrollIntoViewOptions, TouchEvent, TransitionEvent, WheelEvent,
|
||||
AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
|
||||
TransitionEvent, WheelEvent,
|
||||
};
|
||||
|
||||
macro_rules! uncheck_convert {
|
||||
@@ -199,16 +195,20 @@ impl From<&TransitionEvent> for TransitionData {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mounted")]
|
||||
impl From<&web_sys::Element> for MountedData {
|
||||
fn from(e: &web_sys::Element) -> Self {
|
||||
MountedData::new(e.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderedElementBacking for web_sys::Element {
|
||||
#[cfg(feature = "mounted")]
|
||||
impl crate::RenderedElementBacking for web_sys::Element {
|
||||
fn get_client_rect(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = MountedResult<euclid::Rect<f64, f64>>>>> {
|
||||
) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = crate::MountedResult<euclid::Rect<f64, f64>>>>,
|
||||
> {
|
||||
let rect = self.get_bounding_client_rect();
|
||||
let result = Ok(euclid::Rect::new(
|
||||
euclid::Point2D::new(rect.left(), rect.top()),
|
||||
@@ -217,33 +217,36 @@ impl RenderedElementBacking for web_sys::Element {
|
||||
Box::pin(async { result })
|
||||
}
|
||||
|
||||
fn get_raw_element(&self) -> MountedResult<&dyn std::any::Any> {
|
||||
fn get_raw_element(&self) -> crate::MountedResult<&dyn std::any::Any> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn scroll_to(
|
||||
&self,
|
||||
behavior: ScrollBehavior,
|
||||
) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
|
||||
behavior: crate::ScrollBehavior,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::MountedResult<()>>>> {
|
||||
match behavior {
|
||||
ScrollBehavior::Instant => self.scroll_into_view_with_scroll_into_view_options(
|
||||
ScrollIntoViewOptions::new().behavior(web_sys::ScrollBehavior::Instant),
|
||||
crate::ScrollBehavior::Instant => self.scroll_into_view_with_scroll_into_view_options(
|
||||
web_sys::ScrollIntoViewOptions::new().behavior(web_sys::ScrollBehavior::Instant),
|
||||
),
|
||||
ScrollBehavior::Smooth => self.scroll_into_view_with_scroll_into_view_options(
|
||||
ScrollIntoViewOptions::new().behavior(web_sys::ScrollBehavior::Smooth),
|
||||
crate::ScrollBehavior::Smooth => self.scroll_into_view_with_scroll_into_view_options(
|
||||
web_sys::ScrollIntoViewOptions::new().behavior(web_sys::ScrollBehavior::Smooth),
|
||||
),
|
||||
}
|
||||
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
|
||||
fn set_focus(
|
||||
&self,
|
||||
focus: bool,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::MountedResult<()>>>> {
|
||||
let result = self
|
||||
.dyn_ref::<web_sys::HtmlElement>()
|
||||
.ok_or_else(|| MountedError::OperationFailed(Box::new(FocusError(self.into()))))
|
||||
.ok_or_else(|| crate::MountedError::OperationFailed(Box::new(FocusError(self.into()))))
|
||||
.and_then(|e| {
|
||||
(if focus { e.focus() } else { e.blur() })
|
||||
.map_err(|err| MountedError::OperationFailed(Box::new(FocusError(err))))
|
||||
.map_err(|err| crate::MountedError::OperationFailed(Box::new(FocusError(err))))
|
||||
});
|
||||
Box::pin(async { result })
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user