fix(virtual_table): RowIdGetter now handles injected rows

Bug: Tab within injected row was triggering immediate row commit.
This happened because RowIdGetter.getRowId() returned the REAL row ID
for the injected row index, instead of NEW_ROW_ID.

Fix:
- RowIdGetter now takes injected_idx parameter
- Returns NEW_ROW_ID for injected row
- Adjusts indices for rows after injection (row-1)
- num_rows calculation now accounts for injection (+1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
R.Eugenio 2025-12-28 12:50:30 +01:00
parent 4b61c2a119
commit b3a33ec4f3
2 changed files with 34 additions and 3 deletions

View file

@ -80,11 +80,13 @@ pub const PagedDataSource = struct {
if (row == inj_idx) { if (row == inj_idx) {
// Esta es la fila inyectada - leer del edit buffer // Esta es la fila inyectada - leer del edit buffer
if (self.state.row_edit_buffer.getPendingValue(col)) |pending| { if (self.state.row_edit_buffer.getPendingValue(col)) |pending| {
std.debug.print("[PDS-DEBUG] injected row={} col={} pending=\"{s}\"\n", .{ row, col, pending });
const copy_len = @min(pending.len, buf.len); const copy_len = @min(pending.len, buf.len);
@memcpy(buf[0..copy_len], pending[0..copy_len]); @memcpy(buf[0..copy_len], pending[0..copy_len]);
return buf[0..copy_len]; return buf[0..copy_len];
} }
// Sin valor pendiente - retornar vacío (celda nueva) // Sin valor pendiente - retornar vacío (celda nueva)
std.debug.print("[PDS-DEBUG] injected row={} col={} NO pending\n", .{ row, col });
return ""; return "";
} else if (row > inj_idx) { } else if (row > inj_idx) {
// Fila después de la inyección - ajustar índice (-1) // Fila después de la inyección - ajustar índice (-1)

View file

@ -343,10 +343,19 @@ pub fn virtualAdvancedTableRect(
const edited_cell = list_state.getEditingCell().?; const edited_cell = list_state.getEditingCell().?;
const new_value = list_state.getEditText(); const new_value = list_state.getEditText();
std.debug.print("[VT-DEBUG] editor committed: cell=({},{}) value=\"{s}\" hasChanged={} is_injected={}\n", .{
edited_cell.row,
edited_cell.col,
new_value,
list_state.hasValueChanged(),
list_state.row_edit_buffer.is_injected,
});
// Añadir cambio al buffer de fila (NO commit inmediato) // Añadir cambio al buffer de fila (NO commit inmediato)
// El commit real se hace cuando el usuario abandona la fila // El commit real se hace cuando el usuario abandona la fila
if (list_state.hasValueChanged()) { if (list_state.hasValueChanged()) {
list_state.row_edit_buffer.addChange(edited_cell.col, new_value); list_state.row_edit_buffer.addChange(edited_cell.col, new_value);
std.debug.print("[VT-DEBUG] addChange: col={} value=\"{s}\"\n", .{ edited_cell.col, new_value });
// Compatibilidad: mantener flags antiguos // Compatibilidad: mantener flags antiguos
result.cell_committed = true; result.cell_committed = true;
@ -435,23 +444,43 @@ pub fn virtualAdvancedTableRect(
const is_tab = result.navigate_direction == .next_cell or result.navigate_direction == .prev_cell; const is_tab = result.navigate_direction == .next_cell or result.navigate_direction == .prev_cell;
if (is_tab) { if (is_tab) {
// Wrapper para DataProvider que implementa getRowId(usize) -> i64 // Wrapper para DataProvider que implementa getRowId(usize) -> i64
// Tiene en cuenta filas inyectadas (Ctrl+N) y ghost row
const RowIdGetter = struct { const RowIdGetter = struct {
prov: DataProvider, prov: DataProvider,
total: usize, total: usize,
injected_idx: ?usize,
pub fn getRowId(self: @This(), row: usize) i64 { pub fn getRowId(self: @This(), row: usize) i64 {
// Ghost row está al final (índice = total) // Fila inyectada siempre retorna NEW_ROW_ID
if (self.injected_idx) |inj_idx| {
if (row == inj_idx) return table_core.NEW_ROW_ID;
// Filas después de inyección: ajustar índice hacia provider
if (row > inj_idx) {
const adjusted_row = row - 1;
if (adjusted_row >= self.total) return table_core.NEW_ROW_ID;
return self.prov.getRowId(adjusted_row) orelse table_core.NEW_ROW_ID;
}
}
// Ghost row está al final
if (row >= self.total) return table_core.NEW_ROW_ID; if (row >= self.total) return table_core.NEW_ROW_ID;
return self.prov.getRowId(row) orelse table_core.NEW_ROW_ID; return self.prov.getRowId(row) orelse table_core.NEW_ROW_ID;
} }
}; };
const getter = RowIdGetter{ .prov = provider, .total = total_rows }; const getter = RowIdGetter{
.prov = provider,
.total = total_rows,
.injected_idx = list_state.injected_row_idx,
};
const current_row = list_state.getSelectedRow() orelse 0; const current_row = list_state.getSelectedRow() orelse 0;
const forward = result.navigate_direction == .next_cell; const forward = result.navigate_direction == .next_cell;
const num_cols = config.columns.len; const num_cols = config.columns.len;
// VirtualAdvancedTable siempre tiene ghost row disponible // VirtualAdvancedTable siempre tiene ghost row disponible
const num_rows = total_rows + 1; // Si hay inyección, sumar 1 extra al conteo visual
var num_rows = total_rows + 1; // +1 para ghost row
if (list_state.injected_row_idx != null) {
num_rows += 1; // +1 para fila inyectada
}
const plan = table_core.planTabNavigation( const plan = table_core.planTabNavigation(
&list_state.row_edit_buffer, &list_state.row_edit_buffer,