use compact_str::CompactString;
use crate::script::ScriptMsg;
use crate::tdom::node::{NodeArena, NodeId, NodeType};
use super::InputState;
use super::cmd::RenderCmd;
use super::edit::selection_range;
use super::field::{is_range_input, normalize_range_state};
pub(super) fn focus_input(
state: &mut InputState,
node_id: NodeId,
arena: &NodeArena,
msgs: &mut Vec<ScriptMsg>,
cmds: &mut Vec<RenderCmd>,
) {
if state.focused_input == Some(node_id) {
return;
}
state.focused_is_textarea = arena
.get(node_id)
.is_some_and(|n| n.node_type == NodeType::Textarea);
state.focused_is_range = is_range_input(arena, node_id);
if let Some(prev) = state.focused_input.replace(node_id) {
msgs.push(ScriptMsg::BlurInput {
node_id: prev,
value: state.input_value.clone(),
});
state.input_sel_anchor = None;
cmds.push(RenderCmd::RenderInput {
node_id: prev,
value: std::mem::take(&mut state.input_value),
cursor: std::mem::take(&mut state.input_cursor),
focused: false,
selection: None,
});
}
let value = match arena.get(node_id) {
Some(n) if n.node_type == NodeType::Textarea => n.text_content(arena).into(),
Some(n) => n
.attributes
.get("value")
.cloned()
.map(CompactString::from)
.unwrap_or_default(),
None => CompactString::new(""),
};
let mut cursor = value.chars().count();
state.input_value = value;
if state.focused_is_range {
normalize_range_state(state, arena, node_id);
cursor = state.input_value.chars().count();
}
state.input_cursor = cursor;
state.input_sel_anchor = None;
cmds.push(RenderCmd::RenderInput {
node_id,
value: state.input_value.clone(),
cursor,
focused: true,
selection: None,
});
cmds.push(RenderCmd::Focus {
node_id: Some(node_id),
});
msgs.push(ScriptMsg::FocusInput { node_id });
}
pub(super) fn blur_input(
state: &mut InputState,
msgs: &mut Vec<ScriptMsg>,
cmds: &mut Vec<RenderCmd>,
) {
let Some(focused) = state.focused_input.take() else {
return;
};
state.focused_is_textarea = false;
state.focused_is_range = false;
state.input_sel_anchor = None;
msgs.push(ScriptMsg::BlurInput {
node_id: focused,
value: state.input_value.clone(),
});
cmds.push(RenderCmd::RenderInput {
node_id: focused,
value: std::mem::take(&mut state.input_value),
cursor: std::mem::take(&mut state.input_cursor),
focused: false,
selection: None,
});
cmds.push(RenderCmd::Focus { node_id: None });
}
pub(super) fn push_input_render(state: &InputState, focused: NodeId, cmds: &mut Vec<RenderCmd>) {
cmds.push(RenderCmd::RenderInput {
node_id: focused,
value: state.input_value.clone(),
cursor: state.input_cursor,
focused: true,
selection: state
.input_sel_anchor
.map(|anchor| selection_range(anchor, state.input_cursor)),
});
}
pub fn apply_focus_requests(
state: &mut InputState,
cmds: Vec<RenderCmd>,
arena: Option<&NodeArena>,
msgs: &mut Vec<ScriptMsg>,
) -> Vec<RenderCmd> {
let mut out: Vec<RenderCmd> = Vec::with_capacity(cmds.len());
for cmd in cmds {
match cmd {
RenderCmd::FocusRequest { node_id } => {
if let Some(arena) = arena {
focus_input(state, node_id, arena, msgs, &mut out);
}
}
RenderCmd::BlurRequest { node_id } => {
if state.focused_input == Some(node_id) {
blur_input(state, msgs, &mut out);
}
}
other => out.push(other),
}
}
out
}