From 7938e77b400db11eeac64379fa5d089757532f61 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 27 Sep 2026 22:50:19 +0300 Subject: [PATCH] fix(grid): clicking inside the open cell editor no longer discards the edit (Closes #1006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _onGridPointerDown cancelled any in-progress cell edit on every pointer-down inside the grid, including inside the GridCellEditor that's currently open — since #983 replaced per-cell GestureDetectors with one grid-level Listener, there's no longer a narrower gesture region that would have kept those events from reaching this handler in the first place. Repositioning the caret or selecting text inside the editor therefore silently closed it and discarded whatever the user had typed. Now hit-tests the pointer-down position first: if it lands on the cell currently being edited, the grid leaves it alone and lets the editor's own gesture handling deal with it. A press on a different cell still cancels the edit as before. --- lib/features/workspace/result_grid_view.dart | 11 ++ .../workspace/result_grid_cell_ux_test.dart | 103 ++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/lib/features/workspace/result_grid_view.dart b/lib/features/workspace/result_grid_view.dart index 42b2853..f0a879e 100644 --- a/lib/features/workspace/result_grid_view.dart +++ b/lib/features/workspace/result_grid_view.dart @@ -1255,6 +1255,17 @@ class _VirtualResultGridState extends material.State { if (_sortedRows.isEmpty || widget.columns.isEmpty) return; if (_editingCell != null) { + final downCell = _cellAtOffset( + localPosition: event.localPosition, + rowHeight: rowHeight, + ); + if (downCell == _editingCell) { + // The press landed on the cell currently being edited (e.g. to move + // the caret or select text inside the open GridCellEditor) — let the + // editor's own gesture handling deal with it instead of cancelling + // the in-progress edit out from under the user (#1006). + return; + } _cancelEdit(); } diff --git a/test/features/workspace/result_grid_cell_ux_test.dart b/test/features/workspace/result_grid_cell_ux_test.dart index 11d7484..6b69a41 100644 --- a/test/features/workspace/result_grid_cell_ux_test.dart +++ b/test/features/workspace/result_grid_cell_ux_test.dart @@ -1,6 +1,8 @@ +import 'package:flutter/gestures.dart' show PointerDeviceKind; import 'package:flutter/material.dart' as material; import 'package:flutter_test/flutter_test.dart'; import 'package:querya_desktop/features/workspace/data_grid_staging_buffer.dart'; +import 'package:querya_desktop/features/workspace/grid_cell_editor.dart'; import 'package:querya_desktop/features/workspace/result_grid_view.dart'; import '../../support/querya_theme_test_shell.dart'; @@ -113,4 +115,105 @@ void main() { expect(textCursors, isEmpty); }); }); + + group('VirtualResultGrid cell editor', () { + testWidgets( + 'clicking inside the open editor does not discard the in-progress edit (#1006)', + (tester) async { + final staging = DataGridStagingBuffer( + columns: const ['id', 'name'], + rows: const [ + ['1', 'Ada'], + ], + ); + addTearDown(staging.dispose); + + await tester.pumpWidget( + queryaThemeTestShell( + child: material.SizedBox( + width: 800, + height: 400, + child: VirtualResultGrid( + columns: const ['id', 'name'], + rows: const [ + ['1', 'Ada'], + ], + stagingBuffer: staging, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final cellCenter = tester.getCenter(find.text('Ada')); + await tester.tapAt(cellCenter, kind: PointerDeviceKind.mouse); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(cellCenter, kind: PointerDeviceKind.mouse); + await tester.pumpAndSettle(); + + expect(find.byType(GridCellEditor), findsOneWidget, + reason: 'double-click should open the inline editor'); + + await tester.enterText(find.byType(material.TextField), 'Alicia'); + await tester.pump(); + + // Click inside the open editor — e.g. to reposition the caret — must + // not close it or discard the value typed so far. + await tester.tapAt( + tester.getCenter(find.byType(GridCellEditor)), + kind: PointerDeviceKind.mouse, + ); + await tester.pumpAndSettle(); + + expect(find.byType(GridCellEditor), findsOneWidget, + reason: 'a click inside the editor must not cancel the edit'); + expect(find.text('Alicia'), findsOneWidget, + reason: 'the typed value must survive a click inside the editor'); + }); + + testWidgets('clicking a different cell while editing cancels the edit', + (tester) async { + final staging = DataGridStagingBuffer( + columns: const ['id', 'name'], + rows: const [ + ['1', 'Ada'], + ['2', 'Bob'], + ], + ); + addTearDown(staging.dispose); + + await tester.pumpWidget( + queryaThemeTestShell( + child: material.SizedBox( + width: 800, + height: 400, + child: VirtualResultGrid( + columns: const ['id', 'name'], + rows: const [ + ['1', 'Ada'], + ['2', 'Bob'], + ], + stagingBuffer: staging, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final cellCenter = tester.getCenter(find.text('Ada')); + await tester.tapAt(cellCenter, kind: PointerDeviceKind.mouse); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(cellCenter, kind: PointerDeviceKind.mouse); + await tester.pumpAndSettle(); + expect(find.byType(GridCellEditor), findsOneWidget); + + await tester.tapAt( + tester.getCenter(find.text('Bob')), + kind: PointerDeviceKind.mouse, + ); + await tester.pumpAndSettle(); + + expect(find.byType(GridCellEditor), findsNothing); + }); + }); }