use rdom_core::NodeId;
use unicode_segmentation::UnicodeSegmentation;
use crate::TuiDom;
use crate::render::paint_pass::ChromeText;
pub fn value(dom: &TuiDom, input: NodeId) -> String {
crate::node::child_text(dom, input)
}
pub fn set_value(dom: &mut TuiDom, input: NodeId, new_value: &str) {
note_default_value(dom, input);
clear_user_edited(dom, input);
let _ = dom.set_attribute(input, "value", new_value);
let _ = crate::node::install_text_content(dom, input, new_value);
}
pub fn seed_all(dom: &mut TuiDom) {
let inputs: Vec<NodeId> = collect_inputs(dom, dom.root());
for id in inputs {
if !crate::node::is_text_input(dom, id) {
continue;
}
let want = dom
.node(id)
.get_attribute("value")
.unwrap_or("")
.to_string();
let have = value(dom, id);
let has_text_child = dom
.node(id)
.child_nodes()
.any(|c| c.node_type() == rdom_core::NodeType::Text);
if !has_text_child || want != have {
let _ = crate::node::install_text_content(dom, id, &want);
}
note_default_value(dom, id);
}
let textareas: Vec<NodeId> = collect_textareas(dom, dom.root());
for id in textareas {
let has_text_child = dom
.node(id)
.child_nodes()
.any(|c| c.node_type() == rdom_core::NodeType::Text);
if !has_text_child {
let _ = crate::node::install_text_content(dom, id, "");
}
note_default_value(dom, id);
}
}
pub(crate) fn keeps_default_value(dom: &TuiDom, id: NodeId) -> bool {
match dom.node(id).tag_name() {
Some("textarea") => true,
Some("input") => crate::node::is_text_input(dom, id) || is_range_input(dom, id),
_ => false,
}
}
fn is_range_input(dom: &TuiDom, id: NodeId) -> bool {
dom.input_type_state(id) == Some(rdom_core::InputTypeState::Range)
}
fn live_value(dom: &TuiDom, id: NodeId) -> String {
if is_range_input(dom, id) {
return dom
.node(id)
.get_attribute("value")
.unwrap_or("")
.to_string();
}
value(dom, id)
}
pub(crate) fn note_default_value(dom: &mut TuiDom, control: NodeId) {
if !keeps_default_value(dom, control) {
return;
}
let known = dom
.node(control)
.ext()
.is_some_and(|e| e.default_value.is_some());
if known {
return;
}
let current = live_value(dom, control);
if let Some(ext) = dom.node_mut(control).ext_mut() {
ext.default_value = Some(current);
}
}
pub(crate) fn reset_to_default(dom: &mut TuiDom, control: NodeId) {
clear_user_edited(dom, control);
let Some(default) = dom
.node(control)
.ext()
.and_then(|e| e.default_value.clone())
else {
return;
};
if is_range_input(dom, control) {
if default.is_empty() {
let _ = dom.remove_attribute(control, "value");
} else {
let _ = dom.set_attribute(control, "value", &default);
}
return;
}
let _ = crate::node::install_text_content(dom, control, &default);
if dom.node(control).tag_name() == Some("input") {
let _ = dom.set_attribute(control, "value", &default);
}
}
pub(crate) fn clear_user_edited(dom: &mut TuiDom, control: NodeId) {
if let Some(state) = dom
.node_mut(control)
.ext_mut()
.and_then(|e| e.form_state.existing_mut())
{
state.value_user_edited = false;
crate::runtime::state_writes::note();
}
}
pub fn ensure_seeded(dom: &mut TuiDom, id: NodeId) {
let has_text_child = dom
.node(id)
.child_nodes()
.any(|c| c.node_type() == rdom_core::NodeType::Text);
if has_text_child {
return;
}
match dom.node(id).tag_name() {
Some("input") if crate::node::is_text_input(dom, id) => {
let want = dom
.node(id)
.get_attribute("value")
.unwrap_or("")
.to_string();
let _ = crate::node::install_text_content(dom, id, &want);
}
Some("textarea") => {
let _ = crate::node::install_text_content(dom, id, "");
}
_ => {}
}
}
pub fn mirror_to_attribute(dom: &mut TuiDom, editable: NodeId) {
if dom.node(editable).tag_name() != Some("input") {
return;
}
let live = value(dom, editable);
let _ = dom.set_attribute(editable, "value", &live);
}
fn collect_inputs(dom: &TuiDom, root: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
walk_by_tag(dom, root, "input", &mut out);
out
}
fn collect_textareas(dom: &TuiDom, root: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
walk_by_tag(dom, root, "textarea", &mut out);
out
}
fn walk_by_tag(dom: &TuiDom, id: NodeId, tag: &str, out: &mut Vec<NodeId>) {
if dom.node(id).tag_name() == Some(tag) {
out.push(id);
}
for child in dom.node(id).child_nodes() {
walk_by_tag(dom, child.id(), tag, out);
}
}
fn is_password(dom: &TuiDom, id: NodeId) -> bool {
dom.input_type_state(id) == Some(rdom_core::InputTypeState::Password)
}
pub(crate) fn password_inline_chrome(dom: &TuiDom, id: NodeId, _width: u16) -> Option<ChromeText> {
if !is_password(dom, id) {
return None;
}
Some(ChromeText {
text: value(dom, id).graphemes(true).map(|_| "\u{2022}").collect(),
fg: None,
})
}
#[cfg(test)]
mod tests;