feat(datagrid): 支持按单元格选区删除记录

- 统一单元格选区与复选框行的删除目标和计数,按记录行去重处理
- 保留复选框优先级,新增行直接移除,已有记录进入待提交删除
- 隔离页内查找高亮,并在刷新、数据重载或上下文切换时清理旧选区
- 补充选区删除、刷新保护、查找隔离和新增行回归测试
This commit is contained in:
Syngnat
2026-07-20 10:21:01 +08:00
parent ae0eb03c21
commit cb5f6a668f
7 changed files with 336 additions and 16 deletions

View File

@@ -6,10 +6,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import DataGrid, {
attachDataGridVirtualEditRenderVersion,
buildDataGridCommitChangeSet,
collectDataGridCellSelectionRowKeys,
GONAVI_ROW_KEY,
hasDataGridVirtualEditRenderVersionChanged,
} from './DataGrid';
import { resetDataGridDdlViewSharedStateForTests } from './useDataGridDdlView';
import DataGridPageFind from './DataGridPageFind';
import DataGridToolbarFrame from './DataGridToolbarFrame';
import { V2CellContextMenuView, V2ColumnHeaderContextMenuView, V2TableGroupContextMenuView } from './V2TableContextMenu';
import { setCurrentLanguage, t } from '../i18n';
@@ -512,6 +514,21 @@ const commitColumnGuard = (columnName: string) => (
columnName !== GONAVI_ROW_KEY && columnName !== ORACLE_ROWID_LOCATOR_COLUMN
);
describe('DataGrid cell selection row keys', () => {
it('deduplicates every record covered by a rectangular cell selection', () => {
const cellKeys = Array.from({ length: 6 }, (_, rowIndex) => (
['id', 'user_id', 'app_key'].map((columnName) => `row-${rowIndex + 1}\u0001${columnName}`)
)).flat();
expect(collectDataGridCellSelectionRowKeys([
...cellKeys,
'row-3\u0001id',
'malformed-cell-key',
'\u0001empty-row-key',
])).toEqual(['row-1', 'row-2', 'row-3', 'row-4', 'row-5', 'row-6']);
});
});
describe('DataGrid commit change set', () => {
it('uses unique locator values instead of falling back to the whole row', () => {
const result = buildDataGridCommitChangeSet({
@@ -1136,6 +1153,250 @@ describe('DataGrid DDL interactions', () => {
renderer!.unmount();
});
it('deletes every record represented by a cell-only column selection', async () => {
messageApi.info.mockResolvedValue(undefined);
let renderer: ReactTestRenderer;
await act(async () => {
renderer = create(
<DataGrid
data={[
{ __gonavi_row_key__: 'row-1', id: 1, name: 'Ada' },
{ __gonavi_row_key__: 'row-2', id: 2, name: 'Linus' },
]}
columnNames={['id', 'name']}
loading={false}
tableName="users"
dbName="main"
connectionId="conn-1"
pkColumns={['id']}
/>,
);
});
await waitForEffects();
await act(async () => {
renderer!.root.findByType(DataGridToolbarFrame).props.onToggleCellEditMode();
});
await waitForEffects();
const nameColumn = testRenderState.latestColumns.find((column) => column.key === 'name');
expect(nameColumn?.editable).toBe(true);
const headerProps = nameColumn.onHeaderCell(nameColumn);
await act(async () => {
headerProps.onClickCapture({
target: { closest: vi.fn(() => null) },
currentTarget: { querySelector: vi.fn(() => null) },
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
});
});
let toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.selectedRowKeysLength).toBe(0);
expect(toolbar.props.selectedCellsSize).toBe(2);
expect(toolbar.props.deleteTargetRowCount).toBe(2);
expect(findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.disabled).toBeFalsy();
await act(async () => {
toolbar.props.onRefresh();
});
await waitForEffects();
toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.selectedCellsSize).toBe(0);
expect(toolbar.props.deleteTargetRowCount).toBe(0);
expect(findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.disabled).toBe(true);
await act(async () => {
headerProps.onClickCapture({
target: { closest: vi.fn(() => null) },
currentTarget: { querySelector: vi.fn(() => null) },
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
});
});
await act(async () => {
findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.onClick();
});
await waitForEffects();
toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.pendingChangeCount).toBe(2);
expect(toolbar.props.selectedCellsSize).toBe(0);
expect(toolbar.props.deleteTargetRowCount).toBe(0);
expect(findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.disabled).toBe(true);
expect(
testRenderState.latestTableProps.dataSource.map((row: Record<string, unknown>) => (
testRenderState.latestTableProps.rowClassName(row)
)),
).toEqual(['row-deleted', 'row-deleted']);
renderer!.unmount();
});
it('does not treat page-find highlighting as a deletable cell selection', async () => {
messageApi.info.mockResolvedValue(undefined);
let renderer: ReactTestRenderer;
await act(async () => {
renderer = create(
<DataGrid
data={[
{ __gonavi_row_key__: 'row-1', id: 1, name: 'Ada' },
{ __gonavi_row_key__: 'row-2', id: 2, name: 'Linus' },
]}
columnNames={['id', 'name']}
loading={false}
tableName="users"
dbName="main"
connectionId="conn-1"
pkColumns={['id']}
/>,
);
});
await waitForEffects();
await act(async () => {
renderer!.root.findByType(DataGridToolbarFrame).props.onToggleCellEditMode();
});
await act(async () => {
renderer!.root.findByType(DataGridPageFind).props.onPageFindTextChange('Ada');
});
await waitForEffects();
const pageFind = renderer!.root.findByType(DataGridPageFind);
expect(pageFind.props.matchCount).toBeGreaterThan(0);
await act(async () => {
pageFind.props.onNavigateNext();
});
await waitForEffects();
const toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.cellEditMode).toBe(true);
expect(toolbar.props.selectedCellsSize).toBe(1);
expect(toolbar.props.deleteTargetRowCount).toBe(0);
expect(findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.disabled).toBe(true);
renderer!.unmount();
});
it('keeps checkbox-selected rows as the delete target when a cell selection also exists', async () => {
messageApi.info.mockResolvedValue(undefined);
let renderer: ReactTestRenderer;
await act(async () => {
renderer = create(
<DataGrid
data={[
{ __gonavi_row_key__: 'row-1', id: 1, name: 'Ada' },
{ __gonavi_row_key__: 'row-2', id: 2, name: 'Linus' },
]}
columnNames={['id', 'name']}
loading={false}
tableName="users"
dbName="main"
connectionId="conn-1"
pkColumns={['id']}
/>,
);
});
await waitForEffects();
await act(async () => {
renderer!.root.findByType(DataGridToolbarFrame).props.onToggleCellEditMode();
});
const nameColumn = testRenderState.latestColumns.find((column) => column.key === 'name');
const headerProps = nameColumn.onHeaderCell(nameColumn);
await act(async () => {
headerProps.onClickCapture({
target: { closest: vi.fn(() => null) },
currentTarget: { querySelector: vi.fn(() => null) },
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
});
testRenderState.latestTableProps.rowSelection.onChange(['row-1']);
});
await waitForEffects();
let toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.selectedCellsSize).toBe(2);
expect(toolbar.props.selectedRowKeysLength).toBe(1);
expect(toolbar.props.deleteTargetRowCount).toBe(1);
await act(async () => {
toolbar.props.onDeleteSelected();
});
await waitForEffects();
toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.pendingChangeCount).toBe(1);
expect(toolbar.props.selectedCellsSize).toBe(0);
expect(
testRenderState.latestTableProps.dataSource.map((row: Record<string, unknown>) => (
testRenderState.latestTableProps.rowClassName(row)
)),
).toEqual(['row-deleted', '']);
renderer!.unmount();
});
it('removes a newly added record selected only through its cells without leaving a pending delete', async () => {
messageApi.info.mockResolvedValue(undefined);
let renderer: ReactTestRenderer;
await act(async () => {
renderer = create(
<DataGrid
data={[]}
columnNames={['id', 'name']}
loading={false}
tableName="users"
dbName="main"
connectionId="conn-1"
pkColumns={['id']}
/>,
);
});
await waitForEffects();
await act(async () => {
renderer!.root.findByType(DataGridToolbarFrame).props.onAddRow();
});
await waitForEffects();
expect(testRenderState.latestTableProps.dataSource).toHaveLength(1);
expect(renderer!.root.findByType(DataGridToolbarFrame).props.pendingChangeCount).toBe(1);
await act(async () => {
renderer!.root.findByType(DataGridToolbarFrame).props.onToggleCellEditMode();
});
await waitForEffects();
const nameColumn = testRenderState.latestColumns.find((column) => column.key === 'name');
expect(nameColumn?.editable).toBe(true);
const headerProps = nameColumn.onHeaderCell(nameColumn);
await act(async () => {
headerProps.onClickCapture({
target: { closest: vi.fn(() => null) },
currentTarget: { querySelector: vi.fn(() => null) },
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
});
});
let toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(toolbar.props.selectedRowKeysLength).toBe(0);
expect(toolbar.props.selectedCellsSize).toBe(1);
expect(toolbar.props.deleteTargetRowCount).toBe(1);
await act(async () => {
findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.onClick();
});
await waitForEffects();
toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(testRenderState.latestTableProps.dataSource).toHaveLength(0);
expect(toolbar.props.pendingChangeCount).toBe(0);
expect(toolbar.props.selectedCellsSize).toBe(0);
expect(toolbar.props.deleteTargetRowCount).toBe(0);
expect(findButton(renderer!, t('data_grid.toolbar.delete_selected')).props.disabled).toBe(true);
renderer!.unmount();
});
it('allows sorter arrow clicks through while cell edit mode is active', async () => {
messageApi.info.mockResolvedValue(undefined);
const onSort = vi.fn();

View File

@@ -1299,7 +1299,7 @@ describe('DataGrid layout', () => {
expect(toolbarFrameSource).toContain(`translate('${key}`);
});
[
/translate\('data_grid\.toolbar\.selected_count', \{ count: selectedRowKeysLength \}\)/,
/translate\('data_grid\.toolbar\.selected_count', \{ count: deleteTargetRowCount \}\)/,
/translate\('data_grid\.toolbar\.copy_selection', \{ count: selectedCellsSize \}\)/,
/translate\('data_grid\.toolbar\.copy_selection_columns', \{ count: selectedCellsSize \}\)/,
/translate\('data_grid\.toolbar\.batch_fill', \{ count: selectedCellsSize \}\)/,

View File

@@ -190,6 +190,7 @@ import {
useDataGridI18nLanguage,
makeCellKey,
splitCellKey,
collectDataGridCellSelectionRowKeys,
resolveContextMenuFieldName,
trimSimpleCache,
looksLikeDateTimeText,
@@ -289,6 +290,7 @@ export {
resolveNextGridFilterOperatorForColumnChange,
buildGridFieldSelectOptions,
buildDataGridCommitChangeSet,
collectDataGridCellSelectionRowKeys,
} from './DataGridCore';
// Native scroll events can outlive a pointer gesture on macOS. Wait for a brief
@@ -875,6 +877,8 @@ const DataGrid: React.FC<DataGridProps> = ({
// 批量编辑模式状态
const [cellEditMode, setCellEditMode] = useState(false);
const [selectedCells, setSelectedCells] = useState<Set<string>>(new Set());
const [cellSelectionDeleteEligible, setCellSelectionDeleteEligible] = useState(false);
const cellSelectionSourceDataRef = useRef<Item[] | null>(null);
const [copiedCellPatch, setCopiedCellPatch] = useState<{ sourceRowKey: string; values: Record<string, any> } | null>(null);
const [copiedRowsForPaste, setCopiedRowsForPaste] = useState<Array<Record<string, any>>>([]);
@@ -1664,10 +1668,16 @@ const DataGrid: React.FC<DataGridProps> = ({
});
}, []);
const markCellSelectionDeleteEligible = useCallback((eligible: boolean) => {
cellSelectionSourceDataRef.current = eligible ? data : null;
setCellSelectionDeleteEligible(eligible);
}, [data]);
const resetCellSelection = useCallback((clearState: boolean = true) => {
if (clearState) {
setSelectedCells(new Set());
}
markCellSelectionDeleteEligible(false);
currentSelectionRef.current = new Set();
selectionStartRef.current = null;
pendingCellSelectionStartRef.current = null;
@@ -1686,7 +1696,7 @@ const DataGrid: React.FC<DataGridProps> = ({
cellSelectionAutoScrollRafRef.current = null;
}
updateCellSelection(new Set());
}, [updateCellSelection]);
}, [markCellSelectionDeleteEligible, updateCellSelection]);
const closeCellEditMode = useCallback(() => {
setCellEditMode(false);
@@ -1713,8 +1723,17 @@ const DataGrid: React.FC<DataGridProps> = ({
});
currentSelectionRef.current = nextSelection;
setSelectedCells(nextSelection);
markCellSelectionDeleteEligible(true);
updateCellSelection(nextSelection);
}, [canModifyData, effectiveEditLocator, makeCellKey, resetCellSelection, rowKeyStr, updateCellSelection]);
}, [canModifyData, effectiveEditLocator, makeCellKey, markCellSelectionDeleteEligible, resetCellSelection, rowKeyStr, updateCellSelection]);
const previousSelectionSourceDataRef = useRef(data);
useEffect(() => {
if (previousSelectionSourceDataRef.current === data) return;
previousSelectionSourceDataRef.current = data;
setSelectedRowKeys([]);
resetCellSelection();
}, [data, resetCellSelection]);
useEffect(() => {
closeCellEditModeRef.current = closeCellEditMode;
@@ -1766,6 +1785,7 @@ const DataGrid: React.FC<DataGridProps> = ({
setCopiedCellPatch,
setModifiedRows,
setSelectedCells,
markCellSelectionDeleteEligible,
splitCellKey,
suppressCellSelectionClickRef,
translateDataGrid,
@@ -1812,10 +1832,25 @@ const DataGrid: React.FC<DataGridProps> = ({
setAutoCommitRemainingSeconds(null);
}, []);
const selectedCellRowKeys = useMemo(
() => cellEditMode
&& cellSelectionDeleteEligible
&& cellSelectionSourceDataRef.current === data
? collectDataGridCellSelectionRowKeys(selectedCells)
: [],
[cellEditMode, cellSelectionDeleteEligible, data, selectedCells],
);
const deleteTargetRowKeys = useMemo(
() => selectedRowKeys.length > 0
? selectedRowKeys.map(key => rowKeyStr(key))
: selectedCellRowKeys,
[rowKeyStr, selectedCellRowKeys, selectedRowKeys],
);
const deleteTargetRowCount = deleteTargetRowKeys.length;
const allSelectedAreDeleted = useMemo(() => {
if (selectedRowKeys.length === 0) return false;
return selectedRowKeys.every(key => deletedRowKeys.has(rowKeyStr(key)));
}, [selectedRowKeys, deletedRowKeys, rowKeyStr]);
if (deleteTargetRowKeys.length === 0) return false;
return deleteTargetRowKeys.every(key => deletedRowKeys.has(key));
}, [deleteTargetRowKeys, deletedRowKeys]);
const addedRowKeySet = useMemo(() => {
const next = new Set<string>();
@@ -2136,6 +2171,7 @@ const DataGrid: React.FC<DataGridProps> = ({
setDeletedRowKeys(new Set());
setModifiedColumns({});
setSelectedRowKeys([]);
resetCellSelection();
setCopiedCellPatch(null);
setCopiedRowsForPaste([]);
closeRowEditor();
@@ -2159,6 +2195,7 @@ const DataGrid: React.FC<DataGridProps> = ({
currentConnConfig,
dataSourceContextKey,
isV2Ui,
resetCellSelection,
resetDdlViewState,
resolvedDdlTableName,
viewMode,
@@ -3055,8 +3092,7 @@ const DataGrid: React.FC<DataGridProps> = ({
const handleDeleteSelected = () => {
const addedKeysToRemove: string[] = [];
const baseKeysToDelete: string[] = [];
for (const key of selectedRowKeys) {
const keyStr = rowKeyStr(key);
for (const keyStr of deleteTargetRowKeys) {
if (addedRowKeySet.has(keyStr)) {
addedKeysToRemove.push(keyStr);
} else if (!deletedRowKeys.has(keyStr)) {
@@ -3079,15 +3115,17 @@ const DataGrid: React.FC<DataGridProps> = ({
});
}
setSelectedRowKeys([]);
if (cellEditMode) resetCellSelection();
};
const handleUndoDeleteSelected = () => {
setDeletedRowKeys(prev => {
const newDeleted = new Set(prev);
selectedRowKeys.forEach(key => newDeleted.delete(rowKeyStr(key)));
deleteTargetRowKeys.forEach(key => newDeleted.delete(key));
return newDeleted;
});
setSelectedRowKeys([]);
if (cellEditMode) resetCellSelection();
};
const handlePreviewChanges = useCallback(async () => {
@@ -3914,6 +3952,7 @@ const DataGrid: React.FC<DataGridProps> = ({
const focusPageFindMatch = useCallback((match: DataGridFindMatch) => {
if (!match) return;
const nextSelection = new Set([makeCellKey(match.rowKey, match.columnName)]);
markCellSelectionDeleteEligible(false);
setSelectedCells(nextSelection);
currentSelectionRef.current = nextSelection;
selectionStartRef.current = {
@@ -4003,7 +4042,7 @@ const DataGrid: React.FC<DataGridProps> = ({
applyVisibleFocus();
});
});
}, [applyVirtualHorizontalOffset, enableVirtual, mergedDisplayData, pickVerticalScrollTarget, readVirtualHorizontalOffset, rowKeyStr, updateCellSelection, updateFocusedCell]);
}, [applyVirtualHorizontalOffset, enableVirtual, markCellSelectionDeleteEligible, mergedDisplayData, pickVerticalScrollTarget, readVirtualHorizontalOffset, rowKeyStr, updateCellSelection, updateFocusedCell]);
const handleNavigatePageFind = useCallback((direction: DataGridFindNavigationDirection) => {
const nextIndex = resolveDataGridFindNavigationIndex(activePageFindMatchIndex, pageFindMatches.length, direction);
@@ -4952,6 +4991,7 @@ const DataGrid: React.FC<DataGridProps> = ({
dataContextValue,
dataEditAutoCommitDelayMs,
dataEditCommitMode,
deleteTargetRowCount,
dataPanelDirtyRef,
dataPanelIsJson,
dataPanelOpen,

View File

@@ -241,6 +241,15 @@ const splitCellKey = (cellKey: string): { rowKey: string; colName: string } | nu
colName: cellKey.slice(sepIndex + CELL_KEY_SEP.length),
};
};
const collectDataGridCellSelectionRowKeys = (cellKeys: Iterable<string>): string[] => {
const rowKeys = new Set<string>();
for (const cellKey of cellKeys) {
const parsed = splitCellKey(cellKey);
if (!parsed || !parsed.rowKey) continue;
rowKeys.add(parsed.rowKey);
}
return Array.from(rowKeys);
};
export const resolveContextMenuFieldName = (dataIndex: string, title?: string): string => {
const name = String(dataIndex || title || '').trim();
return name;
@@ -1661,6 +1670,7 @@ export {
useDataGridI18nLanguage,
makeCellKey,
splitCellKey,
collectDataGridCellSelectionRowKeys,
trimSimpleCache,
looksLikeDateTimeText,
normalizeDateTimeString,

View File

@@ -111,6 +111,7 @@ const DataGridShell: React.FC<DataGridShellProps> = (props) => {
dataContextValue,
dataEditAutoCommitDelayMs,
dataEditCommitMode,
deleteTargetRowCount,
dataPanelDirtyRef,
dataPanelIsJson,
dataPanelOpen,
@@ -508,6 +509,7 @@ const renderDataTableView = () => (
const handleRefreshGrid = useCallback(() => {
setSelectedRowKeys([]);
resetCellSelection();
const normalizedTableName = String(tableName || '').trim();
const normalizedDbName = String(dbName || '').trim();
if (connectionId && normalizedTableName) {
@@ -518,7 +520,7 @@ const renderDataTableView = () => (
setMetadataReloadVersion((value: number) => value + 1);
}
if (onReload) onReload();
}, [connectionId, dbName, onReload, tableName]);
}, [connectionId, dbName, onReload, resetCellSelection, tableName]);
const handleResetPendingChanges = useCallback(() => {
clearAutoCommitTimer();
@@ -588,6 +590,7 @@ const renderDataTableView = () => (
onToggleFilter={onToggleFilter}
canModifyData={canModifyData}
selectedRowKeysLength={selectedRowKeys.length}
deleteTargetRowCount={deleteTargetRowCount}
allSelectedAreDeleted={allSelectedAreDeleted}
cellEditMode={cellEditMode}
selectedCellsSize={selectedCells.size}

View File

@@ -60,6 +60,7 @@ export interface DataGridToolbarFrameProps {
onToggleFilter?: () => void;
canModifyData: boolean;
selectedRowKeysLength: number;
deleteTargetRowCount: number;
allSelectedAreDeleted: boolean;
cellEditMode: boolean;
selectedCellsSize: number;
@@ -161,6 +162,7 @@ const DataGridToolbarFrame: React.FC<DataGridToolbarFrameProps> = ({
onToggleFilter,
canModifyData,
selectedRowKeysLength,
deleteTargetRowCount,
allSelectedAreDeleted,
cellEditMode,
selectedCellsSize,
@@ -317,11 +319,11 @@ const DataGridToolbarFrame: React.FC<DataGridToolbarFrameProps> = ({
{renderToolbarDivider()}
<Button icon={<PlusOutlined />} onClick={onAddRow}>{translate('data_grid.toolbar.add_row')}</Button>
{allSelectedAreDeleted ? (
<Button icon={<UndoOutlined />} disabled={selectedRowKeysLength === 0} onClick={onUndoDeleteSelected}>{translate('data_grid.toolbar.undo_delete')}</Button>
<Button icon={<UndoOutlined />} disabled={deleteTargetRowCount === 0} onClick={onUndoDeleteSelected}>{translate('data_grid.toolbar.undo_delete')}</Button>
) : (
<Button icon={<DeleteOutlined />} danger disabled={selectedRowKeysLength === 0} onClick={onDeleteSelected}>{translate('data_grid.toolbar.delete_selected')}</Button>
<Button icon={<DeleteOutlined />} danger disabled={deleteTargetRowCount === 0} onClick={onDeleteSelected}>{translate('data_grid.toolbar.delete_selected')}</Button>
)}
{selectedRowKeysLength > 0 && <span style={{ fontSize: '12px', color: '#888' }}>{translate('data_grid.toolbar.selected_count', { count: selectedRowKeysLength })}</span>}
{deleteTargetRowCount > 0 && <span style={{ fontSize: '12px', color: '#888' }}>{translate('data_grid.toolbar.selected_count', { count: deleteTargetRowCount })}</span>}
{renderToolbarDivider()}
<Button
data-grid-cell-editor-action="true"

View File

@@ -43,6 +43,7 @@ type DataGridBatchActionsContext = Record<string, any> & {
>;
setModifiedRows: React.Dispatch<React.SetStateAction<Record<string, any>>>;
setSelectedCells: React.Dispatch<React.SetStateAction<Set<string>>>;
markCellSelectionDeleteEligible: (eligible: boolean) => void;
rowKeyStr: (key: React.Key) => string;
makeCellKey: (rowKey: string, colName: string) => string;
splitCellKey: (cellKey: string) => { rowKey: string; colName: string } | null;
@@ -93,6 +94,7 @@ export const useDataGridBatchActions = (ctx: DataGridBatchActionsContext) => {
setCopiedCellPatch,
setModifiedRows,
setSelectedCells,
markCellSelectionDeleteEligible,
splitCellKey,
suppressCellSelectionClickRef,
translateDataGrid,
@@ -188,6 +190,7 @@ const handleBatchFillCells = useCallback(() => {
// 清除选中状态
setSelectedCells(new Set());
markCellSelectionDeleteEligible(false);
currentSelectionRef.current = new Set();
selectionStartRef.current = null;
isDraggingRef.current = false;
@@ -197,7 +200,7 @@ const handleBatchFillCells = useCallback(() => {
cellSelectionAutoScrollRafRef.current = null;
}
updateCellSelection(new Set());
}, [batchEditValue, batchEditSetNull, addedRows, modifiedRows, rowKeyStr, updateCellSelection, closeBatchEditModal, translateDataGrid]);
}, [batchEditValue, batchEditSetNull, addedRows, modifiedRows, rowKeyStr, updateCellSelection, closeBatchEditModal, markCellSelectionDeleteEligible, translateDataGrid]);
// 事件委托:在容器级别处理单元格拖选;未开启模式时,拖拽超过阈值会自动进入单元格编辑模式。
useEffect(() => {
@@ -443,6 +446,7 @@ const handleBatchFillCells = useCallback(() => {
if (currentSelectionRef.current.size > 0) {
setSelectedCells(new Set(currentSelectionRef.current));
markCellSelectionDeleteEligible(true);
}
};
@@ -489,7 +493,7 @@ const handleBatchFillCells = useCallback(() => {
cellSelectionPointerRef.current = null;
isDraggingRef.current = false;
};
}, [canModifyData, isTableSurfaceActive, displayColumnNames, columnIndexMap, effectiveEditLocator, updateCellSelection]);
}, [canModifyData, isTableSurfaceActive, displayColumnNames, columnIndexMap, effectiveEditLocator, markCellSelectionDeleteEligible, updateCellSelection]);
const handleCopySelectedColumnsFromRow = useCallback(() => {
const activeSelection = currentSelectionRef.current.size > 0 ? currentSelectionRef.current : selectedCells;