modal.rs
⎇
Raw
1//! The one modal shell, a native `<dialog>`.
2//!
3//! Opened with `showModal()`, so the browser supplies the backdrop
4//! (`::backdrop`), the focus trap, inertness for the rest of the page, the
5//! top layer, and Escape-to-close. None of that is hand-rolled here.
6
7use leptos::prelude::*;
8
9#[component]
10pub fn Modal(
11 /// Classes for the card, appended to `modal` (e.g. `"card picker"`).
12 #[prop(into)]
13 class: String,
14 /// Runs when the dialog asks to close: Escape, or a backdrop click.
15 /// The owner clears its own state, which unmounts the dialog.
16 on_close: Callback<()>,
17 children: Children,
18) -> impl IntoView {
19 let dialog: NodeRef<leptos::html::Dialog> = NodeRef::new();
20 Effect::new(move |_| {
21 if let Some(el) = dialog.get() {
22 // `show_modal`, never `show`: only the modal form gets the top
23 // layer, the backdrop and the focus trap.
24 let _ = el.show_modal();
25 }
26 });
27 view! {
28 <dialog
29 node_ref=dialog
30 class="modal-host"
31 // Fires once the browser has closed the dialog, which Escape
32 // does on its own. Tell the owner so its state clears too.
33 // Unmounting an open dialog does not fire this, so the close
34 // path the owner drives cannot loop back here.
35 on:close=move |_| on_close.run(())
36 // Light dismiss: a backdrop click closes the dialog like Escape.
37 closedby="any"
38 >
39 <div class=format!("modal {class}")>{children()}</div>
40 </dialog>
41 }
42}
43