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:
parent
2a92c7530c
commit
454803fe03
2 changed files with 91 additions and 29 deletions
|
|
@ -193,6 +193,7 @@ pub fn advancedTableRect(
|
||||||
.apply_state_colors = true,
|
.apply_state_colors = true,
|
||||||
.draw_row_borders = true,
|
.draw_row_borders = true,
|
||||||
.alternating_rows = config.alternating_rows,
|
.alternating_rows = config.alternating_rows,
|
||||||
|
.edit_buffer = &table_state.row_edit_buffer,
|
||||||
}, &cell_buffer);
|
}, &cell_buffer);
|
||||||
|
|
||||||
// End clipping
|
// 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_out and config.handle_tab) {
|
||||||
if (events.tab_shift) {
|
// Wrapper para obtener row_id por índice (en AdvancedTable, usamos índice como ID)
|
||||||
// Shift+Tab: move left, wrap to previous row
|
const RowIdGetter = struct {
|
||||||
if (table_state.selected_col > 0) {
|
total: usize,
|
||||||
table_state.selectCell(
|
|
||||||
@intCast(@max(0, table_state.selected_row)),
|
pub fn getRowId(self: @This(), row: usize) i64 {
|
||||||
@intCast(table_state.selected_col - 1),
|
// Ghost row está al final
|
||||||
);
|
if (row >= self.total) return table_core.NEW_ROW_ID;
|
||||||
result.selection_changed = true;
|
return @intCast(row);
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
// Tab: move right, wrap to next row
|
|
||||||
if (table_state.selected_col < @as(i32, @intCast(col_count)) - 1) {
|
const getter = RowIdGetter{ .total = row_count };
|
||||||
table_state.selectCell(
|
const current_row: usize = @intCast(@max(0, table_state.selected_row));
|
||||||
@intCast(@max(0, table_state.selected_row)),
|
const current_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||||
@intCast(table_state.selected_col + 1),
|
const forward = !events.tab_shift;
|
||||||
);
|
// AdvancedTable: usar count de filas existentes (no tiene ghost row como VirtualAdvancedTable)
|
||||||
result.selection_changed = true;
|
const num_rows = row_count;
|
||||||
} else if (table_state.selected_row < @as(i32, @intCast(row_count)) - 1 and config.wrap_navigation) {
|
|
||||||
table_state.selectCell(
|
const plan = table_core.planTabNavigation(
|
||||||
@intCast(table_state.selected_row + 1),
|
&table_state.row_edit_buffer,
|
||||||
0,
|
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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,9 @@ pub const AdvancedTableState = struct {
|
||||||
/// Estado de edición embebido (Fase 2 refactor)
|
/// Estado de edición embebido (Fase 2 refactor)
|
||||||
cell_edit: table_core.CellEditState = .{},
|
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)
|
/// Original value before editing (for revert on Escape)
|
||||||
/// NOTA: Mantenemos esto porque CellValue es más rico que buffer crudo
|
/// NOTA: Mantenemos esto porque CellValue es más rico que buffer crudo
|
||||||
original_value: ?CellValue = null,
|
original_value: ?CellValue = null,
|
||||||
|
|
@ -1016,6 +1019,36 @@ pub const AdvancedTableResult = struct {
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
clicked: bool = false,
|
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];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue