Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
925 changes: 468 additions & 457 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ lto = true
codegen-units = 1
strip = true
incremental = false

# Outside of wasm, we need to use our wry/desktop compatible patch for the wasm-bindgen family of libraries
[patch.crates-io]
dioxus = { git = "https://github.com/ealmloff/dioxus", branch = "wasm-bindgen" }
wasm-bindgen = { git = "https://github.com/DioxusLabs/wasm-bindgen-wry" }
wasm-bindgen-futures = { git = "https://github.com/DioxusLabs/wasm-bindgen-wry" }
js-sys = { git = "https://github.com/DioxusLabs/wasm-bindgen-wry" }
web-sys = { git = "https://github.com/DioxusLabs/wasm-bindgen-wry" }
10 changes: 10 additions & 0 deletions preview/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ unic-langid = { version = "0.9", features = ["macros"] }
strum = { version = "0.27.2", features = ["derive"] }
tracing.workspace = true
time = { version = "0.3.44", features = ["std", "macros"] }
wasm-bindgen = "0.2"
js-sys = "0.3"
gloo-timers = { version = "0.3", features = ["futures"] }
futures = "0.3"
web-sys = { version = "0.3", features = [
"Window",
"Document",
"Element",
"HtmlElement",
] }

[build-dependencies]
syntect = "5.2"
Expand Down
13 changes: 6 additions & 7 deletions preview/src/components/progress/variants/main/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
use super::super::component::*;
use dioxus::prelude::*;
use futures::StreamExt;
use gloo_timers::future::IntervalStream;

#[component]
pub fn Demo() -> Element {
let mut progress = use_signal(|| 0);

use_effect(move || {
let mut timer = document::eval(
"setInterval(() => {
dioxus.send(Math.floor(Math.random() * 30));
}, 1000);",
);
spawn(async move {
while let Ok(new_progress) = timer.recv::<usize>().await {
let mut interval = IntervalStream::new(1000);
while interval.next().await.is_some() {
let random_value = (js_sys::Math::random() * 30.0).floor() as usize;
let mut progress = progress.write();
*progress = (*progress + new_progress) % 101;
*progress = (*progress + random_value) % 101;
}
});
});
Expand Down
25 changes: 19 additions & 6 deletions preview/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::components::{separator::Separator, tabs::component::*};
use crate::dioxus_router::LinkProps;
use dioxus::prelude::*;
use dioxus_i18n::prelude::*;
use wasm_bindgen::JsValue;

use std::str::FromStr;
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
Expand Down Expand Up @@ -155,9 +156,17 @@ fn NavigationLayout() -> Element {
return;
}

document::eval(&format!(
"window.top.postMessage({{ 'route': '{route}' }}, '*');"
));
if let Some(window) = web_sys::window() {
if let Ok(Some(top)) = window.top() {
let message = js_sys::Object::new();
let _ = js_sys::Reflect::set(
&message,
&JsValue::from_str("route"),
&JsValue::from_str(&route.to_string()),
);
let _ = top.post_message(&message.into(), "*");
}
}
});

rsx! {
Expand Down Expand Up @@ -339,9 +348,13 @@ fn CheckIcon() -> Element {

fn set_theme(dark_mode: bool) {
let theme = if dark_mode { "dark" } else { "light" };
_ = document::eval(&format!(
"document.documentElement.setAttribute('data-theme', '{theme}');",
));
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
if let Some(doc_el) = document.document_element() {
let _ = doc_el.set_attribute("data-theme", theme);
}
}
}
}

#[component]
Expand Down
17 changes: 17 additions & 0 deletions primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ dioxus-sdk-time = "0.7.0"
time = { version = "0.3.44", features = ["std", "macros", "parsing"] }
num-integer = "0.1.46"
tracing.workspace = true
wasm-bindgen = "0.2"
js-sys = "0.3"
wasm-bindgen-futures = "0.4"
gloo-events = "0.2"
gloo-timers = { version = "0.3", features = ["futures"] }
web-sys = { version = "0.3", features = [
"Window",
"Document",
"Element",
"HtmlElement",
"HtmlInputElement",
"HtmlBodyElement",
"CssStyleDeclaration",
"EventTarget",
"KeyboardEvent",
"PointerEvent",
] }

[build-dependencies]
lazy-js-bundle = "0.6.2"
Expand Down
19 changes: 7 additions & 12 deletions primitives/src/alert_dialog.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Defines the [`AlertDialogRoot`] component and its sub-components.

use crate::focus_trap::{self, FocusTrap};
use crate::use_global_escape_listener;
use crate::{use_animated_open, use_id_or, use_unique_id, FOCUS_TRAP_JS};
use dioxus::document;
Expand Down Expand Up @@ -186,19 +187,13 @@ pub fn AlertDialogContent(props: AlertDialogContentProps) -> Element {

let gen_id = use_unique_id();
let id = use_id_or(gen_id, props.id);
use_effect(move || {
document::eval(&format!(
r#"let dialog = document.getElementById("{id}");
let is_open = {open};

if (is_open) {{
dialog.trap = window.createFocusTrap(dialog);
}}
if (!is_open && dialog.trap) {{
dialog.trap.remove();
dialog.trap = null;
}}"#
));
let mut trap: Signal<Option<FocusTrap>> = use_signal(|| None);
use_effect(move || {
let id_str = id();
let is_open = open();
let mut trap_ref = trap.write();
focus_trap::setup_focus_trap(&id_str, is_open, &mut trap_ref);
});

rsx! {
Expand Down
55 changes: 30 additions & 25 deletions primitives/src/checkbox.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Defines the [`Checkbox`] component and its subcomponents, which manage checkbox inputs with controlled state.

use crate::{use_controlled, use_unique_id};
use dioxus::{document::eval, prelude::*};
use dioxus::prelude::*;
use std::ops::Not;
use wasm_bindgen::JsCast;

/// The state of a [`Checkbox`] component.
#[derive(Debug, Clone, Copy, PartialEq)]
Expand Down Expand Up @@ -236,31 +237,35 @@ fn BubbleInput(
// Update the actual input state to match our virtual state.
use_effect(move || {
let checked = checked();
let js = eval(
r#"
let id = await dioxus.recv();
let action = await dioxus.recv();
let input = document.getElementById(id);

switch(action) {
case "checked":
input.checked = true;
input.indeterminate = false;
break;
case "indeterminate":
input.indeterminate = true;
input.checked = true;
break;
case "unchecked":
input.checked = false;
input.indeterminate = false;
break;
let id_str = id();

let Some(window) = web_sys::window() else {
return;
};
let Some(document) = window.document() else {
return;
};
let Some(element) = document.get_element_by_id(&id_str) else {
return;
};
let Ok(input) = element.dyn_into::<web_sys::HtmlInputElement>() else {
return;
};

match checked {
CheckboxState::Checked => {
input.set_checked(true);
input.set_indeterminate(false);
}
"#,
);

let _ = js.send(id());
let _ = js.send(checked.to_data_state());
CheckboxState::Indeterminate => {
input.set_checked(true);
input.set_indeterminate(true);
}
CheckboxState::Unchecked => {
input.set_checked(false);
input.set_indeterminate(false);
}
}
});

rsx! {
Expand Down
29 changes: 21 additions & 8 deletions primitives/src/context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
use_animated_open, use_controlled, use_effect_cleanup, use_id_or, use_unique_id,
};
use dioxus::prelude::*;
use wasm_bindgen::JsCast;

#[derive(Clone, Copy)]
struct ContextMenuCtx {
Expand Down Expand Up @@ -120,15 +121,27 @@ pub fn ContextMenu(props: ContextMenuProps) -> Element {
});

// If the context menu is open, prevent pointer and scroll events outside of it
let pointer_events_disabled = |disabled| {
if disabled {
dioxus::document::eval(
"document.body.style.pointerEvents = 'none'; document.documentElement.style.overflow = 'hidden';",
);
let pointer_events_disabled = |disabled: bool| {
let Some(window) = web_sys::window() else {
return;
};
let Some(document) = window.document() else {
return;
};

let (pointer_events, overflow) = if disabled {
("none", "hidden")
} else {
dioxus::document::eval(
"document.body.style.pointerEvents = 'auto'; document.documentElement.style.overflow = 'auto';",
);
("auto", "auto")
};

if let Some(body) = document.body() {
let _ = body.style().set_property("pointer-events", pointer_events);
}
if let Some(doc_el) = document.document_element() {
if let Ok(html_el) = doc_el.dyn_into::<web_sys::HtmlElement>() {
let _ = html_el.style().set_property("overflow", overflow);
}
}
};
use_effect(move || {
Expand Down
19 changes: 7 additions & 12 deletions primitives/src/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use dioxus::document;
use dioxus::prelude::*;

use crate::focus_trap::{self, FocusTrap};
use crate::use_global_escape_listener;
use crate::{use_animated_open, use_controlled, use_id_or, use_unique_id, FOCUS_TRAP_JS};

Expand Down Expand Up @@ -229,25 +230,19 @@ pub fn DialogContent(props: DialogContentProps) -> Element {

let gen_id = use_unique_id();
let id = use_id_or(gen_id, props.id);

let mut trap: Signal<Option<FocusTrap>> = use_signal(|| None);
use_effect(move || {
let is_modal = is_modal();
if !is_modal {
// If the dialog is not modal, we don't need to trap focus.
return;
}

document::eval(&format!(
r#"let dialog = document.getElementById("{id}");
let is_open = {open};

if (is_open) {{
dialog.trap = window.createFocusTrap(dialog);
}}
if (!is_open && dialog.trap) {{
dialog.trap.remove();
dialog.trap = null;
}}"#
));
let id_str = id();
let is_open = open();
let mut trap_ref = trap.write();
focus_trap::setup_focus_trap(&id_str, is_open, &mut trap_ref);
});

rsx! {
Expand Down
44 changes: 44 additions & 0 deletions primitives/src/focus_trap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Typed bindings for the focus trap JavaScript library.

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

#[wasm_bindgen]
extern "C" {
/// A focus trap instance that keeps focus within a container element.
pub type FocusTrap;

/// Creates a new focus trap for the given container element.
/// This calls `window.createFocusTrap(container)` defined in focus-trap.js.
#[wasm_bindgen(js_namespace = window, js_name = createFocusTrap)]
pub fn create_focus_trap(container: &web_sys::HtmlElement) -> FocusTrap;

/// Removes the focus trap, restoring normal focus behavior.
#[wasm_bindgen(method)]
pub fn remove(this: &FocusTrap);
}

/// Sets up or tears down a focus trap for the given element.
///
/// When `open` is true, creates a new focus trap. When `open` is false,
/// removes any existing focus trap.
pub fn setup_focus_trap(id: &str, open: bool, trap: &mut Option<FocusTrap>) {
let Some(window) = web_sys::window() else {
return;
};
let Some(document) = window.document() else {
return;
};
let Some(element) = document.get_element_by_id(id) else {
return;
};
let Ok(html_el) = element.dyn_into::<web_sys::HtmlElement>() else {
return;
};

if open {
*trap = Some(create_focus_trap(&html_el));
} else if let Some(t) = trap.take() {
t.remove();
}
}
Loading