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 std::sync::atomic::{AtomicU32, Ordering};
8
9use leptos::prelude::*;
10
11static NEXT_TITLE: AtomicU32 = AtomicU32::new(0);
12
13#[component]
14pub fn Modal(
15 /// Classes for the card, appended to `modal` (e.g. `"card picker"`).
16 #[prop(into)]
17 class: String,
18 /// Runs when the dialog asks to close: Escape, or a backdrop click.
19 /// The owner clears its own state, which unmounts the dialog.
20 on_close: Callback<()>,
21 /// Id of the title element. Default: the first `.modal-title` present on
22 /// mount.
23 #[prop(optional, into)]
24 labelled_by: Option<String>,
25 children: Children,
26) -> impl IntoView {
27 let dialog: NodeRef<leptos::html::Dialog> = NodeRef::new();
28 Effect::new(move |_| {
29 if let Some(el) = dialog.get() {
30 let title = labelled_by.clone().or_else(|| {
31 let t = el.query_selector(".modal-title").ok().flatten()?;
32 if t.id().is_empty() {
33 t.set_id(&format!(
34 "modal-title-{}",
35 NEXT_TITLE.fetch_add(1, Ordering::Relaxed)
36 ));
37 }
38 Some(t.id())
39 });
40 if let Some(id) = title {
41 let _ = el.set_attribute("aria-labelledby", &id);
42 }
43 // `show_modal`, never `show`: only the modal form gets the top
44 // layer, the backdrop and the focus trap.
45 let _ = el.show_modal();
46 }
47 });
48 view! {
49 <dialog
50 node_ref=dialog
51 class="modal-host"
52 // Fires once the browser has closed the dialog, which Escape
53 // does on its own. Tell the owner so its state clears too.
54 // Unmounting an open dialog does not fire this, so the close
55 // path the owner drives cannot loop back here.
56 on:close=move |_| on_close.run(())
57 // Light dismiss: a backdrop click closes the dialog like Escape.
58 closedby="any"
59 >
60 <div class=format!("modal {class}")>{children()}</div>
61 </dialog>
62 }
63}
64