diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 3002a79bb5..f72241e2d4 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -106,6 +106,7 @@ import { cellDetailEditorText, defaultCellDetailTab, formatJsonText, + linkedCellDetailTarget, valueEditorActions, visibleCellDetailTabs, type CellDetailTab, @@ -2192,6 +2193,18 @@ function showCellDetails(rowIndex: number, colIndex: number) { showCellDetail.value = true; } +watch([selectedRange, showCellDetail, isEditingDetail], () => { + const selectedCell = currentSelectedCellPosition(); + const target = linkedCellDetailTarget({ + isOpen: showCellDetail.value, + isEditing: isEditingDetail.value, + selectedCell: selectedCell ? { rowIndex: selectedCell.rowIndex, visibleColIndex: selectedCell.colIndex } : null, + actualColumnIndex, + }); + if (!target) return; + detailCell.value = target; +}); + function openImagePreview(src: string, title: string) { imagePreviewSrc.value = src; imagePreviewTitle.value = title; diff --git a/apps/desktop/src/lib/cellDetailPresentation.ts b/apps/desktop/src/lib/cellDetailPresentation.ts index a0f3e2e93c..8e23e7341b 100644 --- a/apps/desktop/src/lib/cellDetailPresentation.ts +++ b/apps/desktop/src/lib/cellDetailPresentation.ts @@ -5,6 +5,18 @@ export interface CellDetailPresentationOptions { isEditable: boolean; } +export interface LinkedCellDetailOptions { + isOpen: boolean; + isEditing: boolean; + selectedCell: { rowIndex: number; visibleColIndex: number } | null; + actualColumnIndex: (visibleColIndex: number) => number; +} + +export interface CellDetailTarget { + rowIndex: number; + col: number; +} + export function defaultCellDetailTab(): CellDetailTab { return "details"; } @@ -35,6 +47,14 @@ export function valueEditorActions(options: { canSetNull: boolean; canFormatJson return actions; } +export function linkedCellDetailTarget(options: LinkedCellDetailOptions): CellDetailTarget | null { + if (!options.isOpen || options.isEditing || !options.selectedCell) return null; + return { + rowIndex: options.selectedCell.rowIndex, + col: options.actualColumnIndex(options.selectedCell.visibleColIndex), + }; +} + export function isJsonColumnType(columnType: string | undefined): boolean { const base = (columnType ?? "") .trim() diff --git a/packages/app-tests/cellDetailPresentation.test.ts b/packages/app-tests/cellDetailPresentation.test.ts index 7914cb14c0..afd863158e 100644 --- a/packages/app-tests/cellDetailPresentation.test.ts +++ b/packages/app-tests/cellDetailPresentation.test.ts @@ -4,6 +4,7 @@ import { canFormatCellDetailJson, cellDetailEditorText, defaultCellDetailTab, + linkedCellDetailTarget, visibleCellDetailTabs, valueEditorActions, type CellDetailPresentationOptions, @@ -54,3 +55,24 @@ test("cell detail value editor uses cell actions instead of confirm and cancel", ]); assert.deepEqual(valueEditorActions({ canSetNull: false }), ["restoreOriginal"]); }); + +test("cell detail follows the selected grid cell while open", () => { + assert.deepEqual( + linkedCellDetailTarget({ + isOpen: true, + isEditing: false, + selectedCell: { rowIndex: 2, visibleColIndex: 1 }, + actualColumnIndex: (visibleColIndex) => [0, 3, 5][visibleColIndex] ?? visibleColIndex, + }), + { rowIndex: 2, col: 3 }, + ); +}); + +test("cell detail does not follow selection while closed or editing", () => { + const selectedCell = { rowIndex: 2, visibleColIndex: 1 }; + const actualColumnIndex = (visibleColIndex: number) => visibleColIndex; + + assert.equal(linkedCellDetailTarget({ isOpen: false, isEditing: false, selectedCell, actualColumnIndex }), null); + assert.equal(linkedCellDetailTarget({ isOpen: true, isEditing: true, selectedCell, actualColumnIndex }), null); + assert.equal(linkedCellDetailTarget({ isOpen: true, isEditing: false, selectedCell: null, actualColumnIndex }), null); +});