pub mod tabindex;
pub(crate) mod visible;
#[cfg(test)]
mod tests;
use rdom_core::{NodeId, NodeType, Position, Selection};
use crate::node::{TuiNodeExt, is_descendant_or_self};
use crate::{TuiDom, TuiEvent};
pub fn focus_node(dom: &mut TuiDom, new_focus: Option<NodeId>) {
focus_node_with(dom, new_focus, None);
}
pub(crate) fn focus_node_by_pointer(dom: &mut TuiDom, new_focus: Option<NodeId>) {
let evident = new_focus.map(|id| visible::pointer_focus_is_evident(dom, id));
focus_node_with(dom, new_focus, evident);
}
fn focus_node_with(dom: &mut TuiDom, new_focus: Option<NodeId>, visible: Option<bool>) {
let old = dom.focused();
if old == new_focus {
return;
}
if let Some(old_id) = old {
let mut blur = TuiEvent::blur();
if crate::tui_event::dispatch_to_live(dom, old_id, &mut blur) {
let mut out = TuiEvent::focusout();
crate::tui_event::dispatch_to_live(dom, old_id, &mut out);
}
}
let new_focus = new_focus.filter(|&id| dom.contains(id));
dom.set_focused(new_focus);
if let Some(visible) = visible {
dom.set_focus_visible(visible);
}
if let Some(new_id) = new_focus {
seed_caret_for_editable_focus(dom, new_id);
}
if let Some(new_id) = new_focus {
let mut foc = TuiEvent::focus();
if crate::tui_event::dispatch_to_live(dom, new_id, &mut foc) {
let mut fin = TuiEvent::focusin();
crate::tui_event::dispatch_to_live(dom, new_id, &mut fin);
}
}
}
fn seed_caret_for_editable_focus(dom: &mut TuiDom, id: NodeId) {
if !dom.node(id).is_editable() {
return;
}
crate::runtime::builtins::input::ensure_seeded(dom, id);
if let Some(sel) = dom.selection()
&& is_descendant_or_self(dom, sel.focus.node, id)
{
return;
}
let text_child = dom
.node(id)
.child_nodes()
.find(|n| n.node_type() == NodeType::Text)
.map(|n| n.id());
if let Some(text_id) = text_child {
dom.set_selection(Some(Selection::caret(Position::new(text_id, 0))));
}
}
pub fn nearest_focusable_ancestor(dom: &TuiDom, start: NodeId) -> Option<NodeId> {
let mut cur = Some(start);
while let Some(id) = cur {
if tabindex::is_focusable(dom, id) {
return Some(id);
}
cur = dom.node(id).parent_node().map(|p| p.id());
}
None
}