use rdom_core::{NodeId, NodeType};
use unicode_width::UnicodeWidthStr;
use crate::TuiDom;
use crate::layout::Size;
use crate::style::Value;
const CELL_H_PADDING: u16 = 2;
const COLSYNC_ATTR: &str = "data-rdom-colsync";
pub fn size_all_tables(dom: &mut TuiDom) {
let tables = collect_tables(dom, dom.root());
for table in tables {
size_columns(dom, table);
}
}
pub fn size_columns(dom: &mut TuiDom, table: NodeId) {
let rows = collect_rows(dom, table);
if rows.is_empty() {
return;
}
let mut col_widths: Vec<u16> = Vec::new();
for &row_id in &rows {
let cells = collect_cells(dom, row_id);
for (i, &cell) in cells.iter().enumerate() {
let content = text_content_width(dom, cell);
let total = content.saturating_add(CELL_H_PADDING);
if i < col_widths.len() {
col_widths[i] = col_widths[i].max(total);
} else {
col_widths.push(total);
}
}
}
for &row_id in &rows {
let cells = collect_cells(dom, row_id);
for (i, &cell) in cells.iter().enumerate() {
let Some(&w) = col_widths.get(i) else {
continue;
};
if let Some(ext) = dom.node_mut(cell).ext_mut() {
ext.inline_style.width = Some(Value::Specified(Size::Fixed(w)));
}
}
}
let signature = col_widths
.iter()
.map(u16::to_string)
.collect::<Vec<_>>()
.join(",");
if dom.get_attribute(table, COLSYNC_ATTR) != Some(signature.as_str()) {
let _ = dom.set_attribute(table, COLSYNC_ATTR, &signature);
}
}
fn collect_tables(dom: &TuiDom, root: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
walk_tables(dom, root, &mut out);
out
}
fn walk_tables(dom: &TuiDom, id: NodeId, out: &mut Vec<NodeId>) {
if dom.node(id).tag_name() == Some("table") {
out.push(id);
}
for child in dom.node(id).child_nodes() {
walk_tables(dom, child.id(), out);
}
}
fn collect_rows(dom: &TuiDom, table: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
for child in dom.node(table).child_nodes() {
match child.tag_name() {
Some("tr") => out.push(child.id()),
Some("thead") | Some("tbody") | Some("tfoot") => {
for grand in child.child_nodes() {
if grand.tag_name() == Some("tr") {
out.push(grand.id());
}
}
}
_ => {}
}
}
out
}
fn collect_cells(dom: &TuiDom, row: NodeId) -> Vec<NodeId> {
dom.node(row)
.child_nodes()
.filter(|c| matches!(c.tag_name(), Some("td") | Some("th")))
.map(|c| c.id())
.collect()
}
fn text_content_width(dom: &TuiDom, cell: NodeId) -> u16 {
let mut text = String::new();
collect_text(dom, cell, &mut text);
UnicodeWidthStr::width(text.as_str()) as u16
}
fn collect_text(dom: &TuiDom, id: NodeId, out: &mut String) {
for child in dom.node(id).child_nodes() {
if child.node_type() == NodeType::Text {
if let Some(s) = child.node_value() {
out.push_str(s);
}
} else {
collect_text(dom, child.id(), out);
}
}
}
#[cfg(test)]
mod tests;