feat: Paridad Excel-style AdvancedTable ↔ VirtualAdvancedTable
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 <noreply@anthropic.com>
This commit is contained in:
parent
bb2d6a7be1
commit
b2a4081493
5 changed files with 82 additions and 28 deletions
|
|
@ -284,6 +284,33 @@ pub fn handleKeyboard(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.exit, .exit_with_commit => {
|
.exit, .exit_with_commit => {
|
||||||
|
// 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_out = true;
|
||||||
result.tab_shift = events.tab_shift;
|
result.tab_shift = events.tab_shift;
|
||||||
|
|
||||||
|
|
@ -295,6 +322,7 @@ pub fn handleKeyboard(
|
||||||
result.row_changes_count = info.change_count;
|
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)
|
// Operaciones CRUD (Ctrl+N, Ctrl+Delete, Ctrl+B desde el Core)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
if (config.allow_row_operations) {
|
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) {
|
if (events.insert_row) {
|
||||||
const insert_idx: usize = if (table_state.selected_row >= 0)
|
const insert_idx: usize = if (table_state.selected_row >= 0)
|
||||||
@as(usize, @intCast(table_state.selected_row)) + 1 // +1 = debajo
|
@as(usize, @intCast(table_state.selected_row)) + 1 // +1 = debajo
|
||||||
|
|
@ -339,6 +367,10 @@ pub fn handleKeyboard(
|
||||||
table_state.selectCell(new_idx, 0);
|
table_state.selectCell(new_idx, 0);
|
||||||
// Inicializar buffer para nueva fila (Excel-style)
|
// Inicializar buffer para nueva fila (Excel-style)
|
||||||
table_state.row_edit_buffer.startEdit(table_core.NEW_ROW_ID, new_idx, true);
|
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.row_inserted = true;
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
|
|
@ -481,6 +513,10 @@ pub fn handleEditingKeyboard(
|
||||||
// Escape canceló la edición
|
// Escape canceló la edición
|
||||||
if (kb_result.cancelled) {
|
if (kb_result.cancelled) {
|
||||||
table_state.stopEditing();
|
table_state.stopEditing();
|
||||||
|
// Salir del modo inserción si estaba activo
|
||||||
|
if (table_state.nav.isInInsertionMode()) {
|
||||||
|
table_state.nav.exitInsertionMode();
|
||||||
|
}
|
||||||
result.edit_ended = true;
|
result.edit_ended = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -311,8 +311,10 @@ pub fn drawRowsWithDataSource(
|
||||||
const row_id = datasource_arg.getRowId(row_idx);
|
const row_id = datasource_arg.getRowId(row_idx);
|
||||||
|
|
||||||
// Intentar leer del buffer si tiene cambios pendientes
|
// 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 (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| {
|
if (eb.getPendingValue(col_idx)) |pending| {
|
||||||
cell_text = pending;
|
cell_text = pending;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,8 +173,31 @@ pub const NavigationState = struct {
|
||||||
/// Double-click state
|
/// Double-click state
|
||||||
double_click: DoubleClickState = .{},
|
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();
|
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)
|
/// Navega a siguiente celda (Tab)
|
||||||
/// Retorna nueva posición y si navegó o salió del widget
|
/// 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 } {
|
pub fn tabToNextCell(self: *Self, current_row: usize, num_cols: usize, num_rows: usize, wrap: bool) struct { row: usize, col: usize, result: TabNavigateResult } {
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,8 @@ pub fn handleKeyboard(
|
||||||
// Ctrl+N
|
// Ctrl+N
|
||||||
if (events.insert_row) {
|
if (events.insert_row) {
|
||||||
result.insert_row_requested = true;
|
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
|
// Ctrl+Delete/B
|
||||||
|
|
|
||||||
|
|
@ -164,16 +164,10 @@ pub const VirtualAdvancedTableState = struct {
|
||||||
row_edit_buffer: table_core.RowEditBuffer = .{},
|
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();
|
const Self = @This();
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -731,21 +725,19 @@ pub const VirtualAdvancedTableState = struct {
|
||||||
// Métodos de Modo Inserción Cronológico
|
// Métodos de Modo Inserción Cronológico
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
/// Entra en modo inserción
|
/// Entra en modo inserción (delega a nav)
|
||||||
pub fn enterInsertionMode(self: *Self) void {
|
pub fn enterInsertionMode(self: *Self, inserted_id: i64) void {
|
||||||
self.is_insertion_mode = true;
|
self.nav.enterInsertionMode(inserted_id);
|
||||||
self.insertion_session_active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sale del modo inserción
|
/// Sale del modo inserción (delega a nav)
|
||||||
pub fn exitInsertionMode(self: *Self) void {
|
pub fn exitInsertionMode(self: *Self) void {
|
||||||
self.is_insertion_mode = false;
|
self.nav.exitInsertionMode();
|
||||||
self.insertion_session_active = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifica si estamos en modo inserción
|
/// Verifica si estamos en modo inserción (delega a nav)
|
||||||
pub fn isInInsertionMode(self: *const Self) bool {
|
pub fn isInInsertionMode(self: *const Self) bool {
|
||||||
return self.is_insertion_mode;
|
return self.nav.isInInsertionMode();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue