Compare commits
6 commits
e8b4c98d4a
...
d16019d54f
| Author | SHA1 | Date | |
|---|---|---|---|
| d16019d54f | |||
| 0026dbff2a | |||
| 60c3f9d456 | |||
| 91969cb728 | |||
| 9b2cf2a3dd | |||
| 49cf12f7b9 |
6 changed files with 567 additions and 391 deletions
|
|
@ -241,19 +241,31 @@ pub fn toggleSort(current_column, current_direction, clicked_column) SortToggleR
|
|||
### Edición de Celda
|
||||
|
||||
```zig
|
||||
pub const EditKeyboardResult = struct {
|
||||
committed: bool, // Enter presionado
|
||||
cancelled: bool, // Escape 2x
|
||||
reverted: bool, // Escape 1x (revertir a original)
|
||||
navigate_next: bool, // Tab
|
||||
navigate_prev: bool, // Shift+Tab
|
||||
text_changed: bool, // Texto modificado
|
||||
/// Dirección de navegación después de commit
|
||||
pub const NavigateDirection = enum {
|
||||
none, // Sin navegación
|
||||
next_cell, // Tab → siguiente celda
|
||||
prev_cell, // Shift+Tab → celda anterior
|
||||
next_row, // Enter/↓ → siguiente fila
|
||||
prev_row, // ↑ → fila anterior
|
||||
};
|
||||
|
||||
/// Procesa teclado en modo edición
|
||||
pub const EditKeyboardResult = struct {
|
||||
committed: bool, // Enter/Tab/flechas presionados
|
||||
cancelled: bool, // Escape 2x
|
||||
reverted: bool, // Escape 1x (revertir a original)
|
||||
navigate: NavigateDirection, // Dirección de navegación post-commit
|
||||
text_changed: bool, // Texto modificado
|
||||
handled: bool, // IMPORTANTE: evento fue procesado
|
||||
};
|
||||
|
||||
/// Procesa teclado en modo edición (DRY - un solo lugar)
|
||||
pub fn handleEditingKeyboard(ctx, edit_buffer, edit_len, edit_cursor, escape_count, original_text) EditKeyboardResult
|
||||
```
|
||||
|
||||
> **IMPORTANTE:** El campo `handled` indica si table_core procesó el evento de teclado.
|
||||
> Esto es crítico para evitar doble procesamiento de Tab (ver "Lecciones Aprendidas").
|
||||
|
||||
### Renderizado
|
||||
|
||||
```zig
|
||||
|
|
@ -353,10 +365,55 @@ Tests incluidos:
|
|||
|
||||
---
|
||||
|
||||
## Lecciones Aprendidas
|
||||
|
||||
### Bug: Color de fila alternando al presionar Tab (2025-12-27)
|
||||
|
||||
**Síntoma:** Al navegar entre celdas con Tab, el color de la fila seleccionada alternaba
|
||||
entre azul (con focus) y marrón/gris (sin focus), independientemente de si se modificaba
|
||||
el contenido.
|
||||
|
||||
**Causa raíz:** Tab se procesaba DOS veces:
|
||||
1. Por VirtualAdvancedTable/CellEditor → navegación entre celdas
|
||||
2. Por main.zig de la aplicación → cambio de focus entre widgets (`ctx.handleTabKey()`)
|
||||
|
||||
Esto causaba que el focus del widget alternara incorrectamente cada frame.
|
||||
|
||||
**Solución (3 partes):**
|
||||
|
||||
1. **table_core.zig:** Añadir campo `handled` a `EditKeyboardResult` para indicar que el
|
||||
evento fue procesado.
|
||||
|
||||
2. **VirtualAdvancedTable:** Verificar `navigate_direction != .none` antes de procesar
|
||||
Tab como `tab_out`. Si la tabla ya procesó Tab para navegación interna, no marcar
|
||||
`tab_out = true`.
|
||||
|
||||
3. **Aplicación (main.zig):** No llamar `ctx.handleTabKey()` cuando el panel activo
|
||||
maneja Tab internamente (ej: cuando `active_tab == .configuracion`).
|
||||
|
||||
**Regla general:**
|
||||
> Cuando un widget procesa teclado internamente en `draw()`, la aplicación debe
|
||||
> respetar esa decisión y NO procesar el mismo evento a nivel global.
|
||||
|
||||
**Código clave:**
|
||||
```zig
|
||||
// VirtualAdvancedTable - handleKeyboard()
|
||||
.tab => {
|
||||
// IMPORTANTE: Solo si CellEditor no procesó Tab
|
||||
if (result.navigate_direction == .none) {
|
||||
result.tab_out = true;
|
||||
result.tab_shift = event.modifiers.shift;
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Historial de Cambios
|
||||
|
||||
| Fecha | Cambio |
|
||||
|-------|--------|
|
||||
| 2025-12-27 | Fix bug colores alternando + campo `handled` en EditKeyboardResult |
|
||||
| 2025-12-27 | Refactorización DRY: lógica común movida a table_core.zig |
|
||||
| 2025-12-26 | table_core.zig creado con funciones de renderizado compartidas |
|
||||
| 2025-12-17 | VirtualAdvancedTable añadido para tablas grandes |
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const Context = @import("../../core/context.zig").Context;
|
|||
const Command = @import("../../core/command.zig");
|
||||
const Layout = @import("../../core/layout.zig");
|
||||
const Style = @import("../../core/style.zig");
|
||||
const table_core = @import("../table_core.zig");
|
||||
|
||||
// Re-export types
|
||||
pub const types = @import("types.zig");
|
||||
|
|
@ -46,6 +47,9 @@ pub const state = @import("state.zig");
|
|||
pub const AdvancedTableState = state.AdvancedTableState;
|
||||
pub const AdvancedTableResult = state.AdvancedTableResult;
|
||||
|
||||
// Re-export table_core types
|
||||
pub const NavigateDirection = table_core.NavigateDirection;
|
||||
|
||||
// =============================================================================
|
||||
// Public API
|
||||
// =============================================================================
|
||||
|
|
@ -919,155 +923,94 @@ fn handleEditingKeyboard(
|
|||
) void {
|
||||
const config = table_schema.config;
|
||||
|
||||
// Escape: cancel editing (1st = revert, 2nd = exit without save)
|
||||
if (ctx.input.keyPressed(.escape)) {
|
||||
table_state.escape_count += 1;
|
||||
if (table_state.escape_count >= 2 or table_state.original_value == null) {
|
||||
// Exit without saving
|
||||
table_state.stopEditing();
|
||||
result.edit_ended = true;
|
||||
} else {
|
||||
// Revert to original value
|
||||
if (table_state.original_value) |orig| {
|
||||
var format_buf: [128]u8 = undefined;
|
||||
const text = orig.format(&format_buf);
|
||||
table_state.startEditing(text);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Obtener texto original para revert
|
||||
var orig_format_buf: [128]u8 = undefined;
|
||||
const original_text: ?[]const u8 = if (table_state.original_value) |orig|
|
||||
orig.format(&orig_format_buf)
|
||||
else
|
||||
null;
|
||||
|
||||
// Reset escape count on any other key
|
||||
table_state.escape_count = 0;
|
||||
// Usar table_core para procesamiento de teclado (DRY)
|
||||
const kb_result = table_core.handleEditingKeyboard(
|
||||
ctx,
|
||||
&table_state.edit_buffer,
|
||||
&table_state.edit_len,
|
||||
&table_state.edit_cursor,
|
||||
&table_state.escape_count,
|
||||
original_text,
|
||||
);
|
||||
|
||||
// Enter: confirm editing
|
||||
if (ctx.input.keyPressed(.enter)) {
|
||||
commitEdit(table_state, table_schema, result);
|
||||
// Si no se procesó ningún evento, salir
|
||||
if (!kb_result.handled) return;
|
||||
|
||||
// Escape canceló la edición
|
||||
if (kb_result.cancelled) {
|
||||
table_state.stopEditing();
|
||||
result.edit_ended = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Tab: confirm and move to next cell
|
||||
if (ctx.input.keyPressed(.tab) and config.handle_tab) {
|
||||
// Commit (Enter, Tab, flechas) y navegación
|
||||
if (kb_result.committed) {
|
||||
commitEdit(table_state, table_schema, result);
|
||||
table_state.stopEditing();
|
||||
result.edit_ended = true;
|
||||
|
||||
// Move to next/prev cell
|
||||
const shift = ctx.input.modifiers.shift;
|
||||
const col_count = table_schema.columns.len;
|
||||
const row_count = table_state.getRowCount();
|
||||
// Procesar navegación después de commit
|
||||
switch (kb_result.navigate) {
|
||||
.next_cell, .prev_cell => {
|
||||
if (!config.handle_tab) return;
|
||||
|
||||
if (shift) {
|
||||
// Shift+Tab: move left
|
||||
if (table_state.selected_col > 0) {
|
||||
table_state.selectCell(
|
||||
@intCast(@max(0, table_state.selected_row)),
|
||||
@intCast(table_state.selected_col - 1),
|
||||
);
|
||||
} else if (table_state.selected_row > 0 and config.wrap_navigation) {
|
||||
table_state.selectCell(
|
||||
@intCast(table_state.selected_row - 1),
|
||||
col_count - 1,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Tab: move right
|
||||
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),
|
||||
);
|
||||
} 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 col_count = table_schema.columns.len;
|
||||
const row_count = table_state.getRowCount();
|
||||
|
||||
// Auto-start editing in new cell if editable
|
||||
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||
if (new_col < table_schema.columns.len and table_schema.columns[new_col].editable) {
|
||||
if (table_state.getRow(@intCast(@max(0, table_state.selected_row)))) |row| {
|
||||
const value = row.get(table_schema.columns[new_col].name);
|
||||
var format_buf: [128]u8 = undefined;
|
||||
const text = value.format(&format_buf);
|
||||
table_state.startEditing(text);
|
||||
result.edit_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.selection_changed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cursor movement within edit buffer
|
||||
if (ctx.input.keyPressed(.left)) {
|
||||
if (table_state.edit_cursor > 0) {
|
||||
table_state.edit_cursor -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ctx.input.keyPressed(.right)) {
|
||||
if (table_state.edit_cursor < table_state.edit_len) {
|
||||
table_state.edit_cursor += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ctx.input.keyPressed(.home)) {
|
||||
table_state.edit_cursor = 0;
|
||||
return;
|
||||
}
|
||||
if (ctx.input.keyPressed(.end)) {
|
||||
table_state.edit_cursor = table_state.edit_len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Backspace: delete char before cursor
|
||||
if (ctx.input.keyPressed(.backspace)) {
|
||||
if (table_state.edit_cursor > 0) {
|
||||
// Shift characters left
|
||||
var i: usize = table_state.edit_cursor - 1;
|
||||
while (i < table_state.edit_len - 1) : (i += 1) {
|
||||
table_state.edit_buffer[i] = table_state.edit_buffer[i + 1];
|
||||
}
|
||||
table_state.edit_len -= 1;
|
||||
table_state.edit_cursor -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete: delete char at cursor
|
||||
if (ctx.input.keyPressed(.delete)) {
|
||||
if (table_state.edit_cursor < table_state.edit_len) {
|
||||
// Shift characters left
|
||||
var i: usize = table_state.edit_cursor;
|
||||
while (i < table_state.edit_len - 1) : (i += 1) {
|
||||
table_state.edit_buffer[i] = table_state.edit_buffer[i + 1];
|
||||
}
|
||||
table_state.edit_len -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Character input
|
||||
if (ctx.input.text_input_len > 0) {
|
||||
const text = ctx.input.text_input[0..ctx.input.text_input_len];
|
||||
for (text) |ch| {
|
||||
if (ch >= 32 and ch < 127) {
|
||||
if (table_state.edit_len < types.MAX_EDIT_BUFFER - 1) {
|
||||
// Shift characters right
|
||||
var i: usize = table_state.edit_len;
|
||||
while (i > table_state.edit_cursor) : (i -= 1) {
|
||||
table_state.edit_buffer[i] = table_state.edit_buffer[i - 1];
|
||||
if (kb_result.navigate == .prev_cell) {
|
||||
// Shift+Tab: move left
|
||||
if (table_state.selected_col > 0) {
|
||||
table_state.selectCell(
|
||||
@intCast(@max(0, table_state.selected_row)),
|
||||
@intCast(table_state.selected_col - 1),
|
||||
);
|
||||
} else if (table_state.selected_row > 0 and config.wrap_navigation) {
|
||||
table_state.selectCell(
|
||||
@intCast(table_state.selected_row - 1),
|
||||
col_count - 1,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Tab: move right
|
||||
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),
|
||||
);
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
table_state.edit_buffer[table_state.edit_cursor] = ch;
|
||||
table_state.edit_len += 1;
|
||||
table_state.edit_cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start editing in new cell if editable
|
||||
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||
if (new_col < table_schema.columns.len and table_schema.columns[new_col].editable) {
|
||||
if (table_state.getRow(@intCast(@max(0, table_state.selected_row)))) |row| {
|
||||
const value = row.get(table_schema.columns[new_col].name);
|
||||
var format_buf: [128]u8 = undefined;
|
||||
const text = value.format(&format_buf);
|
||||
table_state.startEditing(text);
|
||||
result.edit_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.selection_changed = true;
|
||||
},
|
||||
.next_row, .prev_row => {
|
||||
// Enter o flechas arriba/abajo: solo commit, sin navegación adicional aquí
|
||||
// (La navegación entre filas se maneja en otro lugar si es necesario)
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,24 +210,34 @@ pub fn detectDoubleClick(
|
|||
// Manejo de teclado para edición
|
||||
// =============================================================================
|
||||
|
||||
/// Dirección de navegación después de edición
|
||||
pub const NavigateDirection = enum {
|
||||
none,
|
||||
next_cell, // Tab
|
||||
prev_cell, // Shift+Tab
|
||||
next_row, // Enter o ↓
|
||||
prev_row, // ↑
|
||||
};
|
||||
|
||||
/// Resultado de procesar teclado en modo edición
|
||||
pub const EditKeyboardResult = struct {
|
||||
/// Se confirmó la edición (Enter)
|
||||
/// Se confirmó la edición (Enter, Tab, flechas)
|
||||
committed: bool = false,
|
||||
/// Se canceló la edición (Escape)
|
||||
/// Se canceló la edición (Escape 2x)
|
||||
cancelled: bool = false,
|
||||
/// Se revirtió al valor original (primer Escape)
|
||||
/// Se revirtió al valor original (Escape 1x)
|
||||
reverted: bool = false,
|
||||
/// Se debe navegar a siguiente celda (Tab)
|
||||
navigate_next: bool = false,
|
||||
/// Se debe navegar a celda anterior (Shift+Tab)
|
||||
navigate_prev: bool = false,
|
||||
/// Dirección de navegación después de commit
|
||||
navigate: NavigateDirection = .none,
|
||||
/// El buffer de edición cambió
|
||||
text_changed: bool = false,
|
||||
/// Indica que se procesó un evento de teclado (para evitar doble procesamiento)
|
||||
handled: bool = false,
|
||||
};
|
||||
|
||||
/// Procesa teclado en modo edición
|
||||
/// Modifica edit_buffer, edit_len, edit_cursor según las teclas
|
||||
/// Retorna resultado con flags de navegación y si se procesó algún evento
|
||||
pub fn handleEditingKeyboard(
|
||||
ctx: *Context,
|
||||
edit_buffer: []u8,
|
||||
|
|
@ -238,113 +248,310 @@ pub fn handleEditingKeyboard(
|
|||
) EditKeyboardResult {
|
||||
var result = EditKeyboardResult{};
|
||||
|
||||
// Escape: cancelar o revertir
|
||||
if (ctx.input.keyPressed(.escape)) {
|
||||
escape_count.* += 1;
|
||||
if (escape_count.* >= 2 or original_text == null) {
|
||||
result.cancelled = true;
|
||||
} else {
|
||||
// Revertir al valor original
|
||||
if (original_text) |orig| {
|
||||
const len = @min(orig.len, edit_buffer.len);
|
||||
@memcpy(edit_buffer[0..len], orig[0..len]);
|
||||
edit_len.* = len;
|
||||
edit_cursor.* = len;
|
||||
result.reverted = true;
|
||||
}
|
||||
// Procesar eventos de tecla
|
||||
for (ctx.input.getKeyEvents()) |event| {
|
||||
if (!event.pressed) continue;
|
||||
|
||||
switch (event.key) {
|
||||
.escape => {
|
||||
escape_count.* += 1;
|
||||
if (escape_count.* >= 2 or original_text == null) {
|
||||
result.cancelled = true;
|
||||
} else {
|
||||
// Revertir al valor original
|
||||
if (original_text) |orig| {
|
||||
const len = @min(orig.len, edit_buffer.len);
|
||||
@memcpy(edit_buffer[0..len], orig[0..len]);
|
||||
edit_len.* = len;
|
||||
edit_cursor.* = len;
|
||||
result.reverted = true;
|
||||
}
|
||||
}
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
.enter => {
|
||||
result.committed = true;
|
||||
result.navigate = .next_row;
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
.tab => {
|
||||
result.committed = true;
|
||||
result.navigate = if (event.modifiers.shift) .prev_cell else .next_cell;
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
.up => {
|
||||
result.committed = true;
|
||||
result.navigate = .prev_row;
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
.down => {
|
||||
result.committed = true;
|
||||
result.navigate = .next_row;
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
.left => {
|
||||
if (edit_cursor.* > 0) edit_cursor.* -= 1;
|
||||
result.handled = true;
|
||||
// Reset escape count
|
||||
escape_count.* = 0;
|
||||
},
|
||||
.right => {
|
||||
if (edit_cursor.* < edit_len.*) edit_cursor.* += 1;
|
||||
result.handled = true;
|
||||
escape_count.* = 0;
|
||||
},
|
||||
.home => {
|
||||
edit_cursor.* = 0;
|
||||
result.handled = true;
|
||||
escape_count.* = 0;
|
||||
},
|
||||
.end => {
|
||||
edit_cursor.* = edit_len.*;
|
||||
result.handled = true;
|
||||
escape_count.* = 0;
|
||||
},
|
||||
.backspace => {
|
||||
if (edit_cursor.* > 0 and edit_len.* > 0) {
|
||||
// Shift characters left
|
||||
const pos = edit_cursor.* - 1;
|
||||
var i: usize = pos;
|
||||
while (i < edit_len.* - 1) : (i += 1) {
|
||||
edit_buffer[i] = edit_buffer[i + 1];
|
||||
}
|
||||
edit_len.* -= 1;
|
||||
edit_cursor.* -= 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
result.handled = true;
|
||||
escape_count.* = 0;
|
||||
},
|
||||
.delete => {
|
||||
if (edit_cursor.* < edit_len.*) {
|
||||
var i: usize = edit_cursor.*;
|
||||
while (i < edit_len.* - 1) : (i += 1) {
|
||||
edit_buffer[i] = edit_buffer[i + 1];
|
||||
}
|
||||
edit_len.* -= 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
result.handled = true;
|
||||
escape_count.* = 0;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reset escape count en cualquier otra tecla
|
||||
escape_count.* = 0;
|
||||
|
||||
// Enter: confirmar
|
||||
if (ctx.input.keyPressed(.enter)) {
|
||||
result.committed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tab: confirmar y navegar
|
||||
if (ctx.input.keyPressed(.tab)) {
|
||||
result.committed = true;
|
||||
if (ctx.input.modifiers.shift) {
|
||||
result.navigate_prev = true;
|
||||
} else {
|
||||
result.navigate_next = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Movimiento del cursor
|
||||
if (ctx.input.keyPressed(.left)) {
|
||||
if (edit_cursor.* > 0) edit_cursor.* -= 1;
|
||||
return result;
|
||||
}
|
||||
if (ctx.input.keyPressed(.right)) {
|
||||
if (edit_cursor.* < edit_len.*) edit_cursor.* += 1;
|
||||
return result;
|
||||
}
|
||||
if (ctx.input.keyPressed(.home)) {
|
||||
edit_cursor.* = 0;
|
||||
return result;
|
||||
}
|
||||
if (ctx.input.keyPressed(.end)) {
|
||||
edit_cursor.* = edit_len.*;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Backspace
|
||||
if (ctx.input.keyPressed(.backspace)) {
|
||||
if (edit_cursor.* > 0) {
|
||||
// Shift characters left
|
||||
var i: usize = edit_cursor.* - 1;
|
||||
while (i < edit_len.* - 1) : (i += 1) {
|
||||
edit_buffer[i] = edit_buffer[i + 1];
|
||||
}
|
||||
edit_len.* -= 1;
|
||||
edit_cursor.* -= 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Delete
|
||||
if (ctx.input.keyPressed(.delete)) {
|
||||
if (edit_cursor.* < edit_len.*) {
|
||||
var i: usize = edit_cursor.*;
|
||||
while (i < edit_len.* - 1) : (i += 1) {
|
||||
edit_buffer[i] = edit_buffer[i + 1];
|
||||
}
|
||||
edit_len.* -= 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Character input
|
||||
if (ctx.input.text_input_len > 0) {
|
||||
const text = ctx.input.text_input[0..ctx.input.text_input_len];
|
||||
for (text) |ch| {
|
||||
if (ch >= 32 and ch < 127) {
|
||||
if (edit_len.* < edit_buffer.len - 1) {
|
||||
// Shift characters right
|
||||
var i: usize = edit_len.*;
|
||||
// Procesar texto ingresado (caracteres imprimibles)
|
||||
const text_input = ctx.input.getTextInput();
|
||||
if (text_input.len > 0) {
|
||||
for (text_input) |ch| {
|
||||
if (edit_len.* < edit_buffer.len - 1) {
|
||||
// Hacer espacio moviendo caracteres hacia la derecha
|
||||
if (edit_cursor.* < edit_len.*) {
|
||||
var i = edit_len.*;
|
||||
while (i > edit_cursor.*) : (i -= 1) {
|
||||
edit_buffer[i] = edit_buffer[i - 1];
|
||||
}
|
||||
edit_buffer[edit_cursor.*] = ch;
|
||||
edit_len.* += 1;
|
||||
edit_cursor.* += 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
edit_buffer[edit_cursor.*] = ch;
|
||||
edit_len.* += 1;
|
||||
edit_cursor.* += 1;
|
||||
result.text_changed = true;
|
||||
result.handled = true;
|
||||
}
|
||||
}
|
||||
escape_count.* = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Edición de fila completa (commit al abandonar fila, estilo Excel)
|
||||
// =============================================================================
|
||||
|
||||
/// Máximo de columnas soportadas para cambios pendientes
|
||||
pub const MAX_PENDING_COLUMNS: usize = 32;
|
||||
|
||||
/// Máximo tamaño de valor por celda
|
||||
pub const MAX_CELL_VALUE_LEN: usize = 256;
|
||||
|
||||
/// ID especial para filas nuevas (ghost row)
|
||||
pub const NEW_ROW_ID: i64 = -1;
|
||||
|
||||
/// Cambio pendiente en una columna
|
||||
pub const PendingCellChange = struct {
|
||||
/// Índice de columna
|
||||
col: usize,
|
||||
/// Valor nuevo (slice al buffer interno)
|
||||
value: []const u8,
|
||||
};
|
||||
|
||||
/// Buffer para acumular cambios de una fila antes de commit
|
||||
/// Usado por los states de los widgets, procesado por funciones de table_core
|
||||
pub const RowEditBuffer = struct {
|
||||
/// ID de la fila siendo editada (NEW_ROW_ID si es ghost row)
|
||||
row_id: i64 = NEW_ROW_ID,
|
||||
|
||||
/// Índice de fila (para navegación)
|
||||
row_index: usize = 0,
|
||||
|
||||
/// Es una fila nueva (ghost row que el usuario está rellenando)
|
||||
is_new_row: bool = false,
|
||||
|
||||
/// Hay cambios pendientes
|
||||
has_changes: bool = false,
|
||||
|
||||
/// Buffers de valores por columna (almacenamiento fijo)
|
||||
value_buffers: [MAX_PENDING_COLUMNS][MAX_CELL_VALUE_LEN]u8 = undefined,
|
||||
|
||||
/// Longitudes de cada valor
|
||||
value_lens: [MAX_PENDING_COLUMNS]usize = [_]usize{0} ** MAX_PENDING_COLUMNS,
|
||||
|
||||
/// Flags: qué columnas tienen cambios
|
||||
changed_cols: [MAX_PENDING_COLUMNS]bool = [_]bool{false} ** MAX_PENDING_COLUMNS,
|
||||
|
||||
/// Número de columnas con cambios
|
||||
change_count: usize = 0,
|
||||
|
||||
/// Inicializa/resetea el buffer para una nueva fila
|
||||
pub fn startEdit(self: *RowEditBuffer, row_id: i64, row_index: usize, is_new: bool) void {
|
||||
self.row_id = row_id;
|
||||
self.row_index = row_index;
|
||||
self.is_new_row = is_new;
|
||||
self.has_changes = false;
|
||||
self.change_count = 0;
|
||||
for (0..MAX_PENDING_COLUMNS) |i| {
|
||||
self.changed_cols[i] = false;
|
||||
self.value_lens[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Añade un cambio pendiente para una columna
|
||||
pub fn addChange(self: *RowEditBuffer, col: usize, value: []const u8) void {
|
||||
if (col >= MAX_PENDING_COLUMNS) return;
|
||||
|
||||
// Copiar valor al buffer interno
|
||||
const len = @min(value.len, MAX_CELL_VALUE_LEN);
|
||||
@memcpy(self.value_buffers[col][0..len], value[0..len]);
|
||||
self.value_lens[col] = len;
|
||||
|
||||
// Marcar como cambiado
|
||||
if (!self.changed_cols[col]) {
|
||||
self.changed_cols[col] = true;
|
||||
self.change_count += 1;
|
||||
}
|
||||
|
||||
self.has_changes = true;
|
||||
}
|
||||
|
||||
/// Obtiene el valor pendiente de una columna (si hay cambio)
|
||||
pub fn getPendingValue(self: *const RowEditBuffer, col: usize) ?[]const u8 {
|
||||
if (col >= MAX_PENDING_COLUMNS) return null;
|
||||
if (!self.changed_cols[col]) return null;
|
||||
return self.value_buffers[col][0..self.value_lens[col]];
|
||||
}
|
||||
|
||||
/// Limpia el buffer (después de commit o discard)
|
||||
pub fn clear(self: *RowEditBuffer) void {
|
||||
self.row_id = NEW_ROW_ID;
|
||||
self.row_index = 0;
|
||||
self.is_new_row = false;
|
||||
self.has_changes = false;
|
||||
self.change_count = 0;
|
||||
for (0..MAX_PENDING_COLUMNS) |i| {
|
||||
self.changed_cols[i] = false;
|
||||
self.value_lens[i] = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Información para hacer commit de los cambios de una fila
|
||||
/// Retornada cuando el usuario abandona una fila editada
|
||||
pub const RowCommitInfo = struct {
|
||||
/// ID de la fila (NEW_ROW_ID si es INSERT)
|
||||
row_id: i64,
|
||||
|
||||
/// Es INSERT (nueva fila) o UPDATE (fila existente)
|
||||
is_insert: bool,
|
||||
|
||||
/// Lista de cambios (columna, valor)
|
||||
changes: []const PendingCellChange,
|
||||
|
||||
/// Número de cambios
|
||||
change_count: usize,
|
||||
};
|
||||
|
||||
/// Construye la info de commit desde un RowEditBuffer
|
||||
/// El caller debe proveer el array para almacenar los cambios
|
||||
pub fn buildCommitInfo(
|
||||
buffer: *const RowEditBuffer,
|
||||
changes_out: []PendingCellChange,
|
||||
) ?RowCommitInfo {
|
||||
if (!buffer.has_changes) return null;
|
||||
|
||||
var count: usize = 0;
|
||||
for (0..MAX_PENDING_COLUMNS) |col| {
|
||||
if (buffer.changed_cols[col] and count < changes_out.len) {
|
||||
changes_out[count] = .{
|
||||
.col = col,
|
||||
.value = buffer.value_buffers[col][0..buffer.value_lens[col]],
|
||||
};
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return RowCommitInfo{
|
||||
.row_id = buffer.row_id,
|
||||
.is_insert = buffer.is_new_row,
|
||||
.changes = changes_out[0..count],
|
||||
.change_count = count,
|
||||
};
|
||||
}
|
||||
|
||||
/// Verifica si hay que hacer commit antes de editar nueva celda.
|
||||
/// Si la fila cambió y hay cambios pendientes, retorna commit info.
|
||||
/// Siempre inicializa el buffer para la nueva fila.
|
||||
///
|
||||
/// Uso típico en widget:
|
||||
/// ```
|
||||
/// if (table_core.checkRowChangeAndCommit(&state.row_edit_buffer, new_id, new_idx, is_ghost, &changes)) |info| {
|
||||
/// result.row_committed = true;
|
||||
/// result.commit_info = info;
|
||||
/// }
|
||||
/// ```
|
||||
pub fn checkRowChangeAndCommit(
|
||||
buffer: *RowEditBuffer,
|
||||
new_row_id: i64,
|
||||
new_row_index: usize,
|
||||
is_new_row: bool,
|
||||
changes_out: []PendingCellChange,
|
||||
) ?RowCommitInfo {
|
||||
// Si es la misma fila, no hacer nada
|
||||
if (buffer.row_id == new_row_id) return null;
|
||||
|
||||
// Si hay cambios pendientes en la fila anterior, construir commit
|
||||
var commit_info: ?RowCommitInfo = null;
|
||||
if (buffer.has_changes) {
|
||||
commit_info = buildCommitInfo(buffer, changes_out);
|
||||
}
|
||||
|
||||
// Iniciar edición de la nueva fila
|
||||
buffer.startEdit(new_row_id, new_row_index, is_new_row);
|
||||
|
||||
return commit_info;
|
||||
}
|
||||
|
||||
/// Verifica si un row_id corresponde a la ghost row (fila nueva)
|
||||
pub fn isGhostRow(row_id: i64) bool {
|
||||
return row_id == NEW_ROW_ID;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Utilidades
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
//! CellEditor - Editor de celda overlay para edición inline
|
||||
//!
|
||||
//! Dibuja un campo de texto sobre una celda para edición estilo Excel.
|
||||
//! Utiliza el edit_buffer del VirtualAdvancedTableState.
|
||||
//! Utiliza table_core.handleEditingKeyboard() para procesar teclado.
|
||||
|
||||
const std = @import("std");
|
||||
const Context = @import("../../core/context.zig").Context;
|
||||
const Command = @import("../../core/command.zig");
|
||||
const Style = @import("../../core/style.zig");
|
||||
const Input = @import("../../core/input.zig");
|
||||
const types = @import("types.zig");
|
||||
const state_mod = @import("state.zig");
|
||||
const table_core = @import("../table_core.zig");
|
||||
|
||||
const CellGeometry = types.CellGeometry;
|
||||
const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
|
||||
|
||||
// Re-exportar NavigateDirection desde table_core para compatibilidad
|
||||
pub const NavigateDirection = table_core.NavigateDirection;
|
||||
|
||||
/// Colores del editor de celda
|
||||
pub const CellEditorColors = struct {
|
||||
background: Style.Color = Style.Color.rgb(255, 255, 255),
|
||||
|
|
@ -24,8 +27,9 @@ pub const CellEditorColors = struct {
|
|||
};
|
||||
|
||||
/// Resultado del procesamiento del CellEditor
|
||||
/// Usa table_core.NavigateDirection para navegación
|
||||
pub const CellEditorResult = struct {
|
||||
/// El usuario presionó Enter o Tab (commit)
|
||||
/// El usuario presionó Enter, Tab, o flechas (commit)
|
||||
committed: bool = false,
|
||||
|
||||
/// El usuario presionó Escape
|
||||
|
|
@ -37,13 +41,8 @@ pub const CellEditorResult = struct {
|
|||
/// Navegación solicitada después de commit
|
||||
navigate: NavigateDirection = .none,
|
||||
|
||||
pub const NavigateDirection = enum {
|
||||
none,
|
||||
next_cell, // Tab
|
||||
prev_cell, // Shift+Tab
|
||||
next_row, // Enter o ↓
|
||||
prev_row, // ↑
|
||||
};
|
||||
/// Se procesó algún evento de teclado
|
||||
handled: bool = false,
|
||||
};
|
||||
|
||||
/// Dibuja el editor de celda overlay
|
||||
|
|
@ -106,8 +105,28 @@ pub fn drawCellEditor(
|
|||
));
|
||||
}
|
||||
|
||||
// Procesar input de teclado
|
||||
result = handleCellEditorInput(ctx, state);
|
||||
// Procesar input de teclado usando table_core
|
||||
const original_text = state.getOriginalValue();
|
||||
const kb_result = table_core.handleEditingKeyboard(
|
||||
ctx,
|
||||
&state.edit_buffer,
|
||||
&state.edit_buffer_len,
|
||||
&state.edit_cursor,
|
||||
&state.escape_count,
|
||||
if (original_text.len > 0) original_text else null,
|
||||
);
|
||||
|
||||
// Mapear resultado de table_core a CellEditorResult
|
||||
result.committed = kb_result.committed;
|
||||
result.escaped = kb_result.cancelled;
|
||||
result.text_changed = kb_result.text_changed;
|
||||
result.navigate = kb_result.navigate;
|
||||
result.handled = kb_result.handled;
|
||||
|
||||
// Escape con revert no es "escaped" sino solo revertir texto
|
||||
if (kb_result.reverted) {
|
||||
result.escaped = false; // No cerrar editor, solo revertir
|
||||
}
|
||||
|
||||
// Actualizar tiempo de última edición si hubo cambios
|
||||
if (result.text_changed) {
|
||||
|
|
@ -117,116 +136,6 @@ pub fn drawCellEditor(
|
|||
return result;
|
||||
}
|
||||
|
||||
/// Procesa input de teclado para el editor
|
||||
fn handleCellEditorInput(ctx: *Context, state: *VirtualAdvancedTableState) CellEditorResult {
|
||||
var result = CellEditorResult{};
|
||||
|
||||
// Procesar eventos de tecla
|
||||
for (ctx.input.getKeyEvents()) |event| {
|
||||
if (!event.pressed) continue;
|
||||
|
||||
switch (event.key) {
|
||||
.escape => {
|
||||
result.escaped = true;
|
||||
return result;
|
||||
},
|
||||
.enter => {
|
||||
result.committed = true;
|
||||
result.navigate = .next_row;
|
||||
return result;
|
||||
},
|
||||
.tab => {
|
||||
result.committed = true;
|
||||
if (event.modifiers.shift) {
|
||||
result.navigate = .prev_cell;
|
||||
} else {
|
||||
result.navigate = .next_cell;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
.up => {
|
||||
result.committed = true;
|
||||
result.navigate = .prev_row;
|
||||
return result;
|
||||
},
|
||||
.down => {
|
||||
result.committed = true;
|
||||
result.navigate = .next_row;
|
||||
return result;
|
||||
},
|
||||
.left => {
|
||||
// Mover cursor a la izquierda
|
||||
if (state.edit_cursor > 0) {
|
||||
state.edit_cursor -= 1;
|
||||
}
|
||||
},
|
||||
.right => {
|
||||
// Mover cursor a la derecha
|
||||
if (state.edit_cursor < state.edit_buffer_len) {
|
||||
state.edit_cursor += 1;
|
||||
}
|
||||
},
|
||||
.home => {
|
||||
state.edit_cursor = 0;
|
||||
},
|
||||
.end => {
|
||||
state.edit_cursor = state.edit_buffer_len;
|
||||
},
|
||||
.backspace => {
|
||||
if (state.edit_cursor > 0 and state.edit_buffer_len > 0) {
|
||||
// Eliminar caracter antes del cursor
|
||||
const pos = state.edit_cursor - 1;
|
||||
// Mover caracteres hacia la izquierda
|
||||
std.mem.copyForwards(
|
||||
u8,
|
||||
state.edit_buffer[pos .. state.edit_buffer_len - 1],
|
||||
state.edit_buffer[pos + 1 .. state.edit_buffer_len],
|
||||
);
|
||||
state.edit_buffer_len -= 1;
|
||||
state.edit_cursor -= 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
},
|
||||
.delete => {
|
||||
if (state.edit_cursor < state.edit_buffer_len) {
|
||||
// Eliminar caracter en el cursor
|
||||
std.mem.copyForwards(
|
||||
u8,
|
||||
state.edit_buffer[state.edit_cursor .. state.edit_buffer_len - 1],
|
||||
state.edit_buffer[state.edit_cursor + 1 .. state.edit_buffer_len],
|
||||
);
|
||||
state.edit_buffer_len -= 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Procesar texto ingresado (caracteres imprimibles)
|
||||
const text_input = ctx.input.getTextInput();
|
||||
if (text_input.len > 0) {
|
||||
// Insertar caracteres en la posición del cursor
|
||||
for (text_input) |c| {
|
||||
if (state.edit_buffer_len < state.edit_buffer.len - 1) {
|
||||
// Hacer espacio moviendo caracteres hacia la derecha
|
||||
if (state.edit_cursor < state.edit_buffer_len) {
|
||||
var i = state.edit_buffer_len;
|
||||
while (i > state.edit_cursor) : (i -= 1) {
|
||||
state.edit_buffer[i] = state.edit_buffer[i - 1];
|
||||
}
|
||||
}
|
||||
state.edit_buffer[state.edit_cursor] = c;
|
||||
state.edit_buffer_len += 1;
|
||||
state.edit_cursor += 1;
|
||||
result.text_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -177,6 +177,13 @@ pub const VirtualAdvancedTableState = struct {
|
|||
/// Flag: celda requiere commit al terminar edición
|
||||
cell_value_changed: bool = false,
|
||||
|
||||
// =========================================================================
|
||||
// Buffer de cambios de fila (commit al abandonar fila, estilo Excel)
|
||||
// =========================================================================
|
||||
|
||||
/// Buffer para acumular cambios de la fila actual antes de commit
|
||||
row_edit_buffer: table_core.RowEditBuffer = .{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
// =========================================================================
|
||||
|
|
@ -713,6 +720,7 @@ pub const VirtualAdvancedTableState = struct {
|
|||
self.edit_buffer_len = 0;
|
||||
self.edit_cursor = 0;
|
||||
self.cell_value_changed = false;
|
||||
self.row_edit_buffer.clear();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub const CellGeometry = types.CellGeometry;
|
|||
pub const DataProvider = data_provider.DataProvider;
|
||||
pub const CellEditorColors = cell_editor.CellEditorColors;
|
||||
pub const CellEditorResult = cell_editor.CellEditorResult;
|
||||
pub const NavigateDirection = cell_editor.NavigateDirection;
|
||||
pub const drawCellEditor = cell_editor.drawCellEditor;
|
||||
pub const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
|
||||
|
||||
|
|
@ -84,35 +85,57 @@ pub const VirtualAdvancedTableResult = struct {
|
|||
clicked: bool = false,
|
||||
|
||||
// =========================================================================
|
||||
// Edición CRUD Excel-style
|
||||
// Edición CRUD Excel-style (commit al abandonar fila)
|
||||
// =========================================================================
|
||||
|
||||
/// Una celda fue modificada (el usuario hizo commit con Enter/Tab)
|
||||
cell_committed: bool = false,
|
||||
/// Una fila fue completada (el usuario cambió de fila, tenía cambios pendientes)
|
||||
/// Cuando es true, row_commit_id y row_changes contienen los datos
|
||||
row_committed: bool = false,
|
||||
|
||||
/// La fila cambió de edición (auto-save requerido)
|
||||
row_changed: bool = false,
|
||||
/// ID de la fila que se hizo commit (NEW_ROW_ID = -1 para inserts)
|
||||
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)
|
||||
/// Buffer estático que sobrevive el frame
|
||||
row_changes: [table_core.MAX_PENDING_COLUMNS]table_core.PendingCellChange = undefined,
|
||||
|
||||
/// Número de cambios en row_changes
|
||||
row_changes_count: usize = 0,
|
||||
|
||||
/// El usuario canceló edición (Escape 2x = descartar fila)
|
||||
row_discarded: bool = false,
|
||||
|
||||
/// Celda que fue editada (si cell_committed = true)
|
||||
edited_cell: ?CellId = null,
|
||||
|
||||
/// Nuevo valor de la celda (si cell_committed = true)
|
||||
edited_value: ?[]const u8 = null,
|
||||
|
||||
/// Fila anterior (si row_changed = true, para auto-save)
|
||||
previous_row: ?usize = null,
|
||||
|
||||
/// Navegación solicitada después de commit
|
||||
navigate_direction: cell_editor.CellEditorResult.NavigateDirection = .none,
|
||||
/// Navegación solicitada después de edición
|
||||
navigate_direction: cell_editor.NavigateDirection = .none,
|
||||
|
||||
/// Tab presionado sin edición activa (pasar focus al siguiente widget)
|
||||
tab_out: bool = false,
|
||||
|
||||
/// Shift estaba presionado con Tab (para tab_out inverso)
|
||||
tab_shift: bool = false,
|
||||
|
||||
// =========================================================================
|
||||
// Compatibilidad (DEPRECADO - usar row_committed)
|
||||
// =========================================================================
|
||||
|
||||
/// @deprecated: Usar row_committed. Mantenido para compatibilidad.
|
||||
cell_committed: bool = false,
|
||||
/// @deprecated
|
||||
row_changed: bool = false,
|
||||
/// @deprecated
|
||||
edited_cell: ?CellId = null,
|
||||
/// @deprecated
|
||||
edited_value: ?[]const u8 = null,
|
||||
/// @deprecated
|
||||
previous_row: ?usize = null,
|
||||
|
||||
/// Obtiene los cambios como slice (helper para compatibilidad)
|
||||
pub fn getRowChanges(self: *const VirtualAdvancedTableResult) []const table_core.PendingCellChange {
|
||||
return self.row_changes[0..self.row_changes_count];
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -286,26 +309,25 @@ pub fn virtualAdvancedTableRect(
|
|||
const edited_cell = list_state.editing_cell.?;
|
||||
const new_value = list_state.getEditText();
|
||||
|
||||
// Check if row changed (for auto-save)
|
||||
if (list_state.last_edited_row) |last_row| {
|
||||
if (edited_cell.row != last_row and list_state.row_dirty) {
|
||||
result.row_changed = true;
|
||||
result.previous_row = last_row;
|
||||
}
|
||||
}
|
||||
// Añadir cambio al buffer de fila (NO commit inmediato)
|
||||
// El commit real se hace cuando el usuario abandona la fila
|
||||
if (list_state.hasValueChanged()) {
|
||||
list_state.row_edit_buffer.addChange(edited_cell.col, new_value);
|
||||
|
||||
// Commit the edit
|
||||
if (list_state.commitEdit()) {
|
||||
// Compatibilidad: mantener flags antiguos
|
||||
result.cell_committed = true;
|
||||
result.edited_cell = edited_cell;
|
||||
result.edited_value = new_value;
|
||||
}
|
||||
|
||||
// Finalizar edición de celda (sin commit a BD)
|
||||
_ = list_state.commitEdit();
|
||||
result.navigate_direction = editor_result.navigate;
|
||||
} else if (editor_result.escaped) {
|
||||
const action = list_state.handleEscape();
|
||||
if (action == .discard_row) {
|
||||
result.row_discarded = true;
|
||||
list_state.row_edit_buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -372,6 +394,33 @@ pub fn virtualAdvancedTableRect(
|
|||
result.double_click_id = list_state.selected_id;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Commit de fila al cambiar de selección
|
||||
// =========================================================================
|
||||
// Si la selección cambió y hay cambios pendientes en otra fila, hacer commit
|
||||
if (list_state.selection_changed and list_state.row_edit_buffer.has_changes) {
|
||||
const new_row_id = list_state.selected_id orelse table_core.NEW_ROW_ID;
|
||||
const new_row_idx = list_state.getSelectedRow() orelse 0;
|
||||
const is_ghost = table_core.isGhostRow(new_row_id);
|
||||
|
||||
// checkRowChangeAndCommit compara row_ids y hace commit si son diferentes
|
||||
if (table_core.checkRowChangeAndCommit(
|
||||
&list_state.row_edit_buffer,
|
||||
new_row_id,
|
||||
new_row_idx,
|
||||
is_ghost,
|
||||
&result.row_changes,
|
||||
)) |commit_info| {
|
||||
result.row_committed = true;
|
||||
result.row_commit_id = commit_info.row_id;
|
||||
result.row_commit_is_insert = commit_info.is_insert;
|
||||
result.row_changes_count = commit_info.change_count;
|
||||
|
||||
// Compatibilidad
|
||||
result.row_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1017,8 +1066,11 @@ fn handleKeyboard(
|
|||
},
|
||||
.tab => {
|
||||
// Tab sin edición activa: indica que el panel debe mover focus
|
||||
result.tab_out = true;
|
||||
result.tab_shift = event.modifiers.shift;
|
||||
// IMPORTANTE: Solo si CellEditor no procesó Tab (evita doble procesamiento)
|
||||
if (result.navigate_direction == .none) {
|
||||
result.tab_out = true;
|
||||
result.tab_shift = event.modifiers.shift;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue