refactor(tables): Add tabToNextCell/tabToPrevCell to both AdvancedTable and VirtualAdvancedTable

Norma #6: Abstract to library level
- Tab navigation with wrap now in library, not application
- AdvancedTableState.tabToNextCell/tabToPrevCell for in-memory data
- VirtualAdvancedTableState.tabToNextCell/tabToPrevCell for paginated data
- DRY: same logic available in both table widgets
This commit is contained in:
reugenio 2025-12-27 01:28:22 +01:00
parent 702c33c13a
commit c2f0fbb19d
2 changed files with 160 additions and 0 deletions

View file

@ -825,6 +825,90 @@ pub const AdvancedTableState = struct {
};
}
// =========================================================================
// Navegación Tab Excel-style (con wrap)
// =========================================================================
/// Resultado de navegación Tab
pub const TabNavigateResult = enum {
/// Navegó a otra celda dentro del widget
navigated,
/// Salió del widget (Tab en última celda o Shift+Tab en primera)
tab_out,
};
/// Navega a siguiente celda (Tab)
/// Si está en última columna, va a primera columna de siguiente fila.
/// Si está en última fila, hace wrap a primera fila o retorna tab_out.
pub fn tabToNextCell(self: *AdvancedTableState, num_cols: usize, wrap_to_start: bool) TabNavigateResult {
if (num_cols == 0) return .tab_out;
const row_count = self.getRowCount();
if (row_count == 0) return .tab_out;
const current_col: usize = if (self.selected_col >= 0) @intCast(self.selected_col) else 0;
const current_row: usize = if (self.selected_row >= 0) @intCast(self.selected_row) else 0;
if (current_col + 1 < num_cols) {
// Siguiente columna en misma fila
self.selected_col = @intCast(current_col + 1);
return .navigated;
}
// Última columna: ir a primera columna de siguiente fila
self.selected_col = 0;
if (current_row + 1 < row_count) {
// Hay siguiente fila
self.selected_row = @intCast(current_row + 1);
return .navigated;
}
// Última fila
if (wrap_to_start) {
self.selected_row = 0;
return .navigated;
}
return .tab_out;
}
/// Navega a celda anterior (Shift+Tab)
/// Si está en primera columna, va a última columna de fila anterior.
/// Si está en primera fila, hace wrap a última fila o retorna tab_out.
pub fn tabToPrevCell(self: *AdvancedTableState, num_cols: usize, wrap_to_end: bool) TabNavigateResult {
if (num_cols == 0) return .tab_out;
const row_count = self.getRowCount();
if (row_count == 0) return .tab_out;
const current_col: usize = if (self.selected_col >= 0) @intCast(self.selected_col) else 0;
const current_row: usize = if (self.selected_row >= 0) @intCast(self.selected_row) else 0;
if (current_col > 0) {
// Columna anterior en misma fila
self.selected_col = @intCast(current_col - 1);
return .navigated;
}
// Primera columna: ir a última columna de fila anterior
self.selected_col = @intCast(num_cols - 1);
if (current_row > 0) {
// Hay fila anterior
self.selected_row = @intCast(current_row - 1);
return .navigated;
}
// Primera fila
if (wrap_to_end) {
self.selected_row = @intCast(row_count - 1);
return .navigated;
}
return .tab_out;
}
// =========================================================================
// Internal Helpers
// =========================================================================

View file

@ -492,6 +492,82 @@ pub const VirtualAdvancedTableState = struct {
}
}
// =========================================================================
// Navegación Tab Excel-style (con wrap)
// =========================================================================
/// Resultado de navegación Tab
pub const TabNavigateResult = enum {
/// Navegó a otra celda dentro del widget
navigated,
/// Salió del widget (Tab en última celda o Shift+Tab en primera)
tab_out,
};
/// Navega a siguiente celda (Tab)
/// Si está en última columna, va a primera columna de siguiente fila.
/// Si está en última fila, hace wrap a primera fila o retorna tab_out.
pub fn tabToNextCell(self: *Self, num_cols: usize, visible_rows: usize, wrap_to_start: bool) TabNavigateResult {
if (num_cols == 0) return .tab_out;
if (self.active_col + 1 < num_cols) {
// Siguiente columna en misma fila
self.active_col += 1;
return .navigated;
}
// Última columna: ir a primera columna de siguiente fila
self.active_col = 0;
if (self.findSelectedInWindow()) |window_idx| {
if (window_idx + 1 < self.current_window.len) {
// Hay siguiente fila
self.moveDown(visible_rows);
return .navigated;
}
}
// Última fila
if (wrap_to_start) {
self.goToStart();
return .navigated;
}
return .tab_out;
}
/// Navega a celda anterior (Shift+Tab)
/// Si está en primera columna, va a última columna de fila anterior.
/// Si está en primera fila, hace wrap a última fila o retorna tab_out.
pub fn tabToPrevCell(self: *Self, num_cols: usize, visible_rows: usize, total_rows: usize, wrap_to_end: bool) TabNavigateResult {
if (num_cols == 0) return .tab_out;
if (self.active_col > 0) {
// Columna anterior en misma fila
self.active_col -= 1;
return .navigated;
}
// Primera columna: ir a última columna de fila anterior
self.active_col = num_cols - 1;
if (self.findSelectedInWindow()) |window_idx| {
if (window_idx > 0) {
// Hay fila anterior
self.moveUp();
return .navigated;
}
}
// Primera fila
if (wrap_to_end) {
self.goToEnd(visible_rows, total_rows);
return .navigated;
}
return .tab_out;
}
/// Obtiene la fila global seleccionada
pub fn getSelectedRow(self: *const Self) ?usize {
if (self.selected_id == null) return null;