diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 1728e16d16..c23da09b78 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -91,9 +91,11 @@ import { import { formatGridSqlLiteral } from "@/lib/dataGridSql"; import { buildVisibleTransposeRows, + nextAppendedTransposeState, nextContextTransposeState, nextKeyboardTransposeState, nextTransposeState, + nextTransposeStateForRecordCount, transposeFieldWidth, transposeScrollLeftForRecord, visibleTransposeRecordWindow, @@ -293,6 +295,7 @@ const imagePreviewSrc = ref(""); const imagePreviewTitle = ref(""); const transposeRowIndex = ref(null); const showTranspose = ref(false); +const preserveTransposeOnNextResult = ref(false); const transposeScrollRef = ref(); const transposeScrollLeft = ref(0); const transposeViewportWidth = ref(0); @@ -1432,7 +1435,7 @@ const { applyCellValue, cancelEdit, onEditKeydown, - addRow, + addRow: addEditorRow, cloneRow, showDeleteRowConfirm, requestDeleteRow, @@ -1515,6 +1518,7 @@ async function onToolbarCommit() { } function onToolbarRollback() { + preserveTransposeOnNextResult.value = showTranspose.value; discardChanges(); emit( "reload", @@ -1527,6 +1531,11 @@ function onToolbarRollback() { ); } +function addRow() { + addEditorRow(); + focusAppendedTransposeRecord(); +} + const sortedRows = computed(() => { let indices = localFilteredRows.value; const q = deferredClientSearchText.value; @@ -2481,6 +2490,22 @@ function scrollTransposeRecordIntoView(rowIndex: number) { }); } +function applyTransposeState(next: { showTranspose: boolean; transposeRowIndex: number | null }) { + showTranspose.value = next.showTranspose; + transposeRowIndex.value = next.transposeRowIndex; + if (next.showTranspose) { + nextTick(updateTransposeViewport); + if (next.transposeRowIndex !== null) scrollTransposeRecordIntoView(next.transposeRowIndex); + } +} + +function focusAppendedTransposeRecord() { + if (!showTranspose.value) return; + nextTick(() => { + applyTransposeState(nextAppendedTransposeState(true, displayItems.value.length)); + }); +} + function onTransposePinnedResizeStart(event: MouseEvent) { event.preventDefault(); const startX = event.clientX; @@ -2578,6 +2603,8 @@ watch(isTransposeMode, (active) => { watch( () => props.result, () => { + const shouldPreserveTranspose = preserveTransposeOnNextResult.value; + preserveTransposeOnNextResult.value = false; if (getResetScrollAfterResult()) { clearResetScrollAfterResult(); resetGridVerticalScroll(); @@ -2585,7 +2612,13 @@ watch( clearCellSelection(); clearRowSelection(); closeCellDetails(); - closeTranspose(); + if (shouldPreserveTranspose) { + applyTransposeState( + nextTransposeStateForRecordCount(showTranspose.value, transposeRowIndex.value, displayItems.value.length), + ); + } else { + closeTranspose(); + } exitTransaction(); }, ); @@ -3393,16 +3426,52 @@ defineExpose({
- {{ cell.display }} + +
diff --git a/apps/desktop/src/lib/dataGridTranspose.ts b/apps/desktop/src/lib/dataGridTranspose.ts index 7878c17562..9541354538 100644 --- a/apps/desktop/src/lib/dataGridTranspose.ts +++ b/apps/desktop/src/lib/dataGridTranspose.ts @@ -18,6 +18,7 @@ export interface DataGridTransposeCell { export interface DataGridVisibleTransposeCell extends DataGridTransposeCell { recordIndex: number; + valueIndex: number; } export interface DataGridTransposeRow { @@ -115,6 +116,24 @@ export function nextKeyboardTransposeState(options: KeyboardTransposeStateOption return { showTranspose: true, transposeRowIndex: anchorRowIndex }; } +export function nextAppendedTransposeState(showTranspose: boolean, totalRecords: number): DataGridTransposeState { + if (!showTranspose || totalRecords <= 0) return { showTranspose: false, transposeRowIndex: null }; + return { showTranspose: true, transposeRowIndex: totalRecords - 1 }; +} + +export function nextTransposeStateForRecordCount( + showTranspose: boolean, + transposeRowIndex: number | null, + totalRecords: number, +): DataGridTransposeState { + if (!showTranspose || totalRecords <= 0) return { showTranspose: false, transposeRowIndex: null }; + const requestedRowIndex = transposeRowIndex ?? 0; + return { + showTranspose: true, + transposeRowIndex: Math.max(0, Math.min(totalRecords - 1, requestedRowIndex)), + }; +} + export function buildTransposeRows(options: BuildTransposeRowsOptions): Array> { return options.columns.map((column, columnIndex) => { return { @@ -149,6 +168,7 @@ export function buildVisibleTransposeRows( return [ { recordIndex, + valueIndex, value, display: options.displayValue(value, column, columnIndex, recordIndex), isNull: value === null, diff --git a/packages/app-tests/dataGridTranspose.test.ts b/packages/app-tests/dataGridTranspose.test.ts index d8b70345a2..bbfb7da91f 100644 --- a/packages/app-tests/dataGridTranspose.test.ts +++ b/packages/app-tests/dataGridTranspose.test.ts @@ -4,8 +4,10 @@ import { buildTransposeRows, buildVisibleTransposeRows, nextContextTransposeState, + nextAppendedTransposeState, nextKeyboardTransposeState, nextTransposeState, + nextTransposeStateForRecordCount, transposeAnchorRowIndex, transposeFieldWidth, transposeScrollLeftForRecord, @@ -149,6 +151,7 @@ test("builds visible transpose rows from mapped source column indexes", () => { }); assert.equal(rows[0].values[0].display, "Ada"); + assert.equal(rows[0].values[0].valueIndex, 1); }); test("keyboard transpose opens from the selected cell row and closes when active", () => { @@ -183,6 +186,28 @@ test("keyboard transpose opens from the selected cell row and closes when active ); }); +test("transpose follows appended rows when already open", () => { + assert.deepEqual(nextAppendedTransposeState(true, 4), { + showTranspose: true, + transposeRowIndex: 3, + }); + assert.deepEqual(nextAppendedTransposeState(false, 4), { + showTranspose: false, + transposeRowIndex: null, + }); +}); + +test("transpose state is preserved and clamped after record refresh", () => { + assert.deepEqual(nextTransposeStateForRecordCount(true, 4, 3), { + showTranspose: true, + transposeRowIndex: 2, + }); + assert.deepEqual(nextTransposeStateForRecordCount(true, 1, 0), { + showTranspose: false, + transposeRowIndex: null, + }); +}); + test("calculates a horizontal record window with spacer widths", () => { assert.deepEqual( visibleTransposeRecordWindow({ diff --git a/packages/app-tests/dataGridTransposeSource.test.ts b/packages/app-tests/dataGridTransposeSource.test.ts index 9fda519d03..961d624dd8 100644 --- a/packages/app-tests/dataGridTransposeSource.test.ts +++ b/packages/app-tests/dataGridTransposeSource.test.ts @@ -10,3 +10,20 @@ test("data grid uses lazy transpose rows and Tab keyboard toggle", () => { assert.match(source, /isToggleTransposeShortcut/); assert.match(source, /settingsStore\.editorSettings\.shortcuts/); }); + +test("transpose cells reuse inline editing controls", () => { + assert.match(source, /canEditCellItem\(displayItems\[cell\.recordIndex\], cell\.valueIndex\)/); + assert.match(source, /startEdit\(displayItems\[cell\.recordIndex\]\.id, cell\.valueIndex\)/); + assert.match(source, /editingCell\?\.rowId === displayItems\[cell\.recordIndex\]\?\.id/); +}); + +test("transpose mode follows appended rows and survives rollback refresh", () => { + assert.match(source, /addRow:\s*addEditorRow/); + assert.match(source, /function addRow\(\)[\s\S]*?focusAppendedTransposeRecord\(\)/); + assert.match(source, /preserveTransposeOnNextResult/); + assert.match( + source, + /function onToolbarRollback\(\)[\s\S]*?preserveTransposeOnNextResult\.value = showTranspose\.value/, + ); + assert.match(source, /nextTransposeStateForRecordCount/); +});