feat(advanced_table): Unify Excel-style editing with VirtualAdvancedTable

- Add row_edit_buffer to AdvancedTableState
- Connect edit_buffer to rendering via drawRowsWithDataSource
- Use DRY planTabNavigation for Tab navigation + auto-commit
- Add Excel-style result fields: row_committed, row_changes, etc.
- Both widgets now share identical commit-on-row-change behavior
This commit is contained in:
reugenio 2025-12-28 01:59:24 +01:00
parent 2a92c7530c
commit 454803fe03
2 changed files with 91 additions and 29 deletions

View file

@ -193,6 +193,7 @@ pub fn advancedTableRect(
.apply_state_colors = true,
.draw_row_borders = true,
.alternating_rows = config.alternating_rows,
.edit_buffer = &table_state.row_edit_buffer,
}, &cell_buffer);
// End clipping
@ -704,40 +705,68 @@ fn handleKeyboard(
}
// =========================================================================
// Tab navigation (usando lógica del Core + config local)
// Tab navigation con commit Excel-style (DRY: lógica en table_core)
// =========================================================================
if (events.tab_out and config.handle_tab) {
if (events.tab_shift) {
// Shift+Tab: move left, wrap to previous row
if (table_state.selected_col > 0) {
table_state.selectCell(
@intCast(@max(0, table_state.selected_row)),
@intCast(table_state.selected_col - 1),
);
result.selection_changed = true;
} else if (table_state.selected_row > 0 and config.wrap_navigation) {
table_state.selectCell(
@intCast(table_state.selected_row - 1),
col_count - 1,
);
result.selection_changed = true;
// Wrapper para obtener row_id por índice (en AdvancedTable, usamos índice como ID)
const RowIdGetter = struct {
total: usize,
pub fn getRowId(self: @This(), row: usize) i64 {
// Ghost row está al final
if (row >= self.total) return table_core.NEW_ROW_ID;
return @intCast(row);
}
} else {
// Tab: move right, wrap to next row
if (table_state.selected_col < @as(i32, @intCast(col_count)) - 1) {
table_state.selectCell(
@intCast(@max(0, table_state.selected_row)),
@intCast(table_state.selected_col + 1),
);
result.selection_changed = true;
} else if (table_state.selected_row < @as(i32, @intCast(row_count)) - 1 and config.wrap_navigation) {
table_state.selectCell(
@intCast(table_state.selected_row + 1),
0,
};
const getter = RowIdGetter{ .total = row_count };
const current_row: usize = @intCast(@max(0, table_state.selected_row));
const current_col: usize = @intCast(@max(0, table_state.selected_col));
const forward = !events.tab_shift;
// AdvancedTable: usar count de filas existentes (no tiene ghost row como VirtualAdvancedTable)
const num_rows = row_count;
const plan = table_core.planTabNavigation(
&table_state.row_edit_buffer,
current_row,
current_col,
col_count,
num_rows,
forward,
config.wrap_navigation,
getter,
&result.row_changes,
);
// Ejecutar el plan
switch (plan.action) {
.move, .move_with_commit => {
table_state.selectCell(plan.new_row, plan.new_col);
result.selection_changed = true;
if (plan.action == .move_with_commit) {
if (plan.commit_info) |info| {
result.row_committed = true;
result.row_commit_id = info.row_id;
result.row_commit_is_insert = info.is_insert;
result.row_changes_count = info.change_count;
}
}
},
.exit, .exit_with_commit => {
result.tab_out = true;
result.tab_shift = events.tab_shift;
if (plan.action == .exit_with_commit) {
if (plan.commit_info) |info| {
result.row_committed = true;
result.row_commit_id = info.row_id;
result.row_commit_is_insert = info.is_insert;
result.row_changes_count = info.change_count;
}
}
},
}
}
// =========================================================================

View file

@ -96,6 +96,9 @@ pub const AdvancedTableState = struct {
/// Estado de edición embebido (Fase 2 refactor)
cell_edit: table_core.CellEditState = .{},
/// Buffer de edición de fila Excel-style (acumula cambios antes de commit)
row_edit_buffer: table_core.RowEditBuffer = .{},
/// Original value before editing (for revert on Escape)
/// NOTA: Mantenemos esto porque CellValue es más rico que buffer crudo
original_value: ?CellValue = null,
@ -1016,6 +1019,36 @@ pub const AdvancedTableResult = struct {
// Focus
clicked: bool = false,
// =========================================================================
// Edición CRUD Excel-style (simétrico con VirtualAdvancedTableResult)
// =========================================================================
/// Una fila fue completada (el usuario cambió de fila, tenía cambios pendientes)
row_committed: bool = false,
/// ID de la fila que se hizo commit (índice en AdvancedTable)
row_commit_id: i64 = table_core.NEW_ROW_ID,
/// Es un INSERT (ghost row) o UPDATE (fila existente)
row_commit_is_insert: bool = false,
/// Cambios de la fila (válidos si row_committed = true)
row_changes: [table_core.MAX_PENDING_COLUMNS]table_core.PendingCellChange = undefined,
/// Número de cambios en row_changes
row_changes_count: usize = 0,
/// Tab presionado para salir del widget
tab_out: bool = false,
/// Shift estaba presionado con Tab
tab_shift: bool = false,
/// Obtiene los cambios como slice
pub fn getRowChanges(self: *const AdvancedTableResult) []const table_core.PendingCellChange {
return self.row_changes[0..self.row_changes_count];
}
};
// =============================================================================