use rdom_core::{ListenerOptions, NodeId};
use crate::tui_event::TuiDispatchExt;
use crate::{TuiDom, TuiEvent};
const MODAL_ATTR: &str = "data-rdom-modal";
const RETURN_VALUE_ATTR: &str = "data-rdom-return-value";
pub fn show(dom: &mut TuiDom, dialog: NodeId) {
let was_open = dom.node(dialog).has_attribute("open");
let _ = dom.set_attribute(dialog, "open", "");
let _ = dom.remove_attribute(dialog, MODAL_ATTR);
if !was_open {
fire_toggle(dom, dialog, rdom_core::ToggleState::Closed);
}
}
pub fn show_modal(dom: &mut TuiDom, dialog: NodeId) {
let was_open = dom.node(dialog).has_attribute("open");
let _ = dom.set_attribute(dialog, "open", "");
let _ = dom.set_attribute(dialog, MODAL_ATTR, "");
crate::runtime::autofocus::focus_within(dom, dialog);
if !was_open {
fire_toggle(dom, dialog, rdom_core::ToggleState::Closed);
}
}
pub fn close(dom: &mut TuiDom, dialog: NodeId, return_value: &str) {
if !dom.node(dialog).has_attribute("open") {
return; }
let _ = dom.remove_attribute(dialog, "open");
let _ = dom.remove_attribute(dialog, MODAL_ATTR);
let _ = dom.set_attribute(dialog, RETURN_VALUE_ATTR, return_value);
fire_toggle(dom, dialog, rdom_core::ToggleState::Open);
let mut ev = TuiEvent::new("close");
ev.event = ev.event.clone().with_bubbles(false);
let _ = dom.dispatch_tui_event(dialog, &mut ev);
}
fn fire_toggle(dom: &mut TuiDom, dialog: NodeId, old_state: rdom_core::ToggleState) {
let new_state = match old_state {
rdom_core::ToggleState::Open => rdom_core::ToggleState::Closed,
rdom_core::ToggleState::Closed => rdom_core::ToggleState::Open,
};
let mut ev = TuiEvent::new("toggle");
ev.event = ev.event.clone().with_bubbles(false);
ev.event.detail = rdom_core::EventDetail::Toggle(Box::new(rdom_core::ToggleDetail {
old_state,
new_state,
}));
let _ = dom.dispatch_tui_event(dialog, &mut ev);
}
pub fn return_value(dom: &TuiDom, dialog: NodeId) -> String {
dom.node(dialog)
.get_attribute(RETURN_VALUE_ATTR)
.unwrap_or("")
.to_string()
}
pub fn set_return_value(dom: &mut TuiDom, dialog: NodeId, value: &str) {
let _ = dom.set_attribute(dialog, RETURN_VALUE_ATTR, value);
}
pub fn is_modal(dom: &TuiDom, dialog: NodeId) -> bool {
dom.node(dialog).has_attribute("open") && dom.node(dialog).has_attribute(MODAL_ATTR)
}
pub fn enclosing_dialog(dom: &TuiDom, id: NodeId) -> Option<NodeId> {
let mut cur = Some(id);
while let Some(n) = cur {
if dom.node(n).tag_name() == Some("dialog") {
return Some(n);
}
cur = dom.node(n).parent_node().map(|p| p.id());
}
None
}
pub fn install(dom: &mut TuiDom) {
let root = dom.root();
dom.add_event_listener(root, "keydown", ListenerOptions::default(), move |ctx| {
if ctx.event.default_prevented() {
return;
}
let Some(focused) = ctx.dom.focused() else {
return;
};
let Some(key) = ctx.event.detail.as_keyboard() else {
return;
};
let no_mods = !key.modifiers.ctrl
&& !key.modifiers.shift
&& !key.modifiers.alt
&& !key.modifiers.meta;
if key.key != "Escape" || !no_mods {
return;
}
let Some(dialog) = enclosing_dialog(ctx.dom, focused) else {
return;
};
if !is_modal(ctx.dom, dialog) {
return;
}
let mut cancel = TuiEvent::new("cancel");
let _ = ctx.dom.dispatch_tui_event(dialog, &mut cancel);
if cancel.event.default_prevented() {
return;
}
let rv = return_value(ctx.dom, dialog);
close(ctx.dom, dialog, &rv);
})
.expect("dialog Esc-cancel listener install");
}
#[cfg(test)]
mod tests;