From b2a408149361cceb7283232e1fb99777da366aac Mon Sep 17 00:00:00 2001 From: "R.Eugenio" Date: Mon, 29 Dec 2025 14:55:30 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Paridad=20Excel-style=20AdvancedTable?= =?UTF-8?q?=20=E2=86=94=20VirtualAdvancedTable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Propuesta Gemini: Unificación funcional entre tablas. 1. Modo inserción centralizado en NavigationState (table_core) - is_insertion_mode, last_inserted_id - enterInsertionMode(), exitInsertionMode(), isInInsertionMode() 2. Auto-edit en Ctrl+N (AdvancedTable) - Tras insertar fila, inicia edición inmediatamente en col 0 3. Auto-insert en Tab (Modo Pro) - En modo inserción, Tab al final de fila inserta nueva fila - Permite meter 50+ líneas sin soltar teclado 4. Fix ghost row clonada - Comparar row_id AND row_index en rendering - Evita que fila insertada y ghost row compartan buffer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/widgets/advanced_table/input.zig | 54 ++++++++++++++++---- src/widgets/table_core/rendering.zig | 4 +- src/widgets/table_core/state.zig | 23 +++++++++ src/widgets/virtual_advanced_table/input.zig | 3 +- src/widgets/virtual_advanced_table/state.zig | 26 ++++------ 5 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/widgets/advanced_table/input.zig b/src/widgets/advanced_table/input.zig index 569ace1..3ecbe81 100644 --- a/src/widgets/advanced_table/input.zig +++ b/src/widgets/advanced_table/input.zig @@ -284,15 +284,43 @@ pub fn handleKeyboard( } }, .exit, .exit_with_commit => { - result.tab_out = true; - result.tab_shift = events.tab_shift; + // MODO PRO: Si estamos en modo inserción y Tab hacia adelante, + // auto-insertar nueva fila en lugar de salir del widget + if (table_state.nav.isInInsertionMode() and forward and config.allow_row_operations) { + // Commit la fila actual si hay cambios + 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; + } + } + // Auto-insertar nueva fila + const insert_idx = current_row + 1; + if (table_state.insertRow(insert_idx)) |new_idx| { + table_state.selectCell(new_idx, 0); + table_state.row_edit_buffer.startEdit(table_core.NEW_ROW_ID, new_idx, true); + table_state.cell_edit.startEditing(new_idx, 0, "", null); + result.row_inserted = true; + result.selection_changed = true; + } else |_| { + // Si falla la inserción, salir normalmente + result.tab_out = true; + result.tab_shift = events.tab_shift; + } + } else { + // Comportamiento normal: salir del widget + 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; + 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; + } } } }, @@ -329,7 +357,7 @@ pub fn handleKeyboard( // Operaciones CRUD (Ctrl+N, Ctrl+Delete, Ctrl+B desde el Core) // ========================================================================= if (config.allow_row_operations) { - // Ctrl+N: Insert row BELOW current row (inyección local) + // Ctrl+N: Insert row BELOW current row + auto-edit (Excel Pro) if (events.insert_row) { const insert_idx: usize = if (table_state.selected_row >= 0) @as(usize, @intCast(table_state.selected_row)) + 1 // +1 = debajo @@ -339,6 +367,10 @@ pub fn handleKeyboard( table_state.selectCell(new_idx, 0); // Inicializar buffer para nueva fila (Excel-style) table_state.row_edit_buffer.startEdit(table_core.NEW_ROW_ID, new_idx, true); + // AUTO-EDIT: Iniciar edición inmediatamente en columna 0 + table_state.cell_edit.startEditing(new_idx, 0, "", null); + // Entrar en modo inserción + table_state.nav.enterInsertionMode(table_core.NEW_ROW_ID); result.row_inserted = true; result.selection_changed = true; } else |_| {} @@ -481,6 +513,10 @@ pub fn handleEditingKeyboard( // Escape canceló la edición if (kb_result.cancelled) { table_state.stopEditing(); + // Salir del modo inserción si estaba activo + if (table_state.nav.isInInsertionMode()) { + table_state.nav.exitInsertionMode(); + } result.edit_ended = true; return; } diff --git a/src/widgets/table_core/rendering.zig b/src/widgets/table_core/rendering.zig index 65ea976..7d044ad 100644 --- a/src/widgets/table_core/rendering.zig +++ b/src/widgets/table_core/rendering.zig @@ -311,8 +311,10 @@ pub fn drawRowsWithDataSource( const row_id = datasource_arg.getRowId(row_idx); // Intentar leer del buffer si tiene cambios pendientes + // FIX: Comparar AMBOS row_id Y row_index para evitar ghost row clonada + // (cuando dos filas tienen el mismo ID -1, como fila insertada y ghost row) if (config.edit_buffer) |eb| { - if (eb.row_id == row_id) { + if (eb.row_id == row_id and eb.row_index == row_idx) { if (eb.getPendingValue(col_idx)) |pending| { cell_text = pending; } diff --git a/src/widgets/table_core/state.zig b/src/widgets/table_core/state.zig index c5148ef..9c0c684 100644 --- a/src/widgets/table_core/state.zig +++ b/src/widgets/table_core/state.zig @@ -173,8 +173,31 @@ pub const NavigationState = struct { /// Double-click state double_click: DoubleClickState = .{}, + /// Modo inserción: sesión de creación de registros (Ctrl+N activo) + is_insertion_mode: bool = false, + + /// ID del último registro insertado en esta sesión + last_inserted_id: i64 = types.NEW_ROW_ID, + const Self = @This(); + /// Entra en modo inserción (Ctrl+N) + pub fn enterInsertionMode(self: *Self, inserted_id: i64) void { + self.is_insertion_mode = true; + self.last_inserted_id = inserted_id; + } + + /// Sale del modo inserción + pub fn exitInsertionMode(self: *Self) void { + self.is_insertion_mode = false; + self.last_inserted_id = types.NEW_ROW_ID; + } + + /// Verifica si está en modo inserción + pub fn isInInsertionMode(self: *const Self) bool { + return self.is_insertion_mode; + } + /// Navega a siguiente celda (Tab) /// Retorna nueva posición y si navegó o salió del widget pub fn tabToNextCell(self: *Self, current_row: usize, num_cols: usize, num_rows: usize, wrap: bool) struct { row: usize, col: usize, result: TabNavigateResult } { diff --git a/src/widgets/virtual_advanced_table/input.zig b/src/widgets/virtual_advanced_table/input.zig index 42a181d..fa5ad61 100644 --- a/src/widgets/virtual_advanced_table/input.zig +++ b/src/widgets/virtual_advanced_table/input.zig @@ -129,7 +129,8 @@ pub fn handleKeyboard( // Ctrl+N if (events.insert_row) { result.insert_row_requested = true; - list_state.enterInsertionMode(); + // Entrar en modo inserción con ID pendiente (panel lo actualizará tras INSERT) + list_state.enterInsertionMode(table_core.NEW_ROW_ID); } // Ctrl+Delete/B diff --git a/src/widgets/virtual_advanced_table/state.zig b/src/widgets/virtual_advanced_table/state.zig index d64c4d3..07a34a8 100644 --- a/src/widgets/virtual_advanced_table/state.zig +++ b/src/widgets/virtual_advanced_table/state.zig @@ -164,16 +164,10 @@ pub const VirtualAdvancedTableState = struct { row_edit_buffer: table_core.RowEditBuffer = .{}, // ========================================================================= - // Modo Inserción Cronológico (reemplaza inyección local) + // Modo Inserción: Ahora centralizado en nav (NavigationState) + // Usar: nav.enterInsertionMode(), nav.exitInsertionMode(), nav.isInInsertionMode() // ========================================================================= - /// True si estamos en modo inserción (Ctrl+N activo) - is_insertion_mode: bool = false, - - /// IDs de filas insertadas en esta sesión (para ORDER BY cronológico) - /// El Panel gestiona esta lista via el DataProvider - insertion_session_active: bool = false, - const Self = @This(); // ========================================================================= @@ -731,21 +725,19 @@ pub const VirtualAdvancedTableState = struct { // Métodos de Modo Inserción Cronológico // ========================================================================= - /// Entra en modo inserción - pub fn enterInsertionMode(self: *Self) void { - self.is_insertion_mode = true; - self.insertion_session_active = true; + /// Entra en modo inserción (delega a nav) + pub fn enterInsertionMode(self: *Self, inserted_id: i64) void { + self.nav.enterInsertionMode(inserted_id); } - /// Sale del modo inserción + /// Sale del modo inserción (delega a nav) pub fn exitInsertionMode(self: *Self) void { - self.is_insertion_mode = false; - self.insertion_session_active = false; + self.nav.exitInsertionMode(); } - /// Verifica si estamos en modo inserción + /// Verifica si estamos en modo inserción (delega a nav) pub fn isInInsertionMode(self: *const Self) bool { - return self.is_insertion_mode; + return self.nav.isInInsertionMode(); } };