From 02e96473f309d03984a03e5a64e1e61bba5040fa Mon Sep 17 00:00:00 2001 From: Univer <68851825+DR-Univer@users.noreply.github.com> Date: Wed, 27 Mar 2024 19:01:31 +0800 Subject: [PATCH] fix(editor): range selector and range drag (#1713) --- .../src/controllers/prompt.controller.ts | 71 ++++++++++++++++++- .../commands/utils/selection-utils.ts | 38 ++++++++-- packages/sheets/src/index.ts | 2 +- .../ui/src/services/editor/editor.service.ts | 8 +++ 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/packages/sheets-formula/src/controllers/prompt.controller.ts b/packages/sheets-formula/src/controllers/prompt.controller.ts index 05558730d2..df448fcb59 100644 --- a/packages/sheets-formula/src/controllers/prompt.controller.ts +++ b/packages/sheets-formula/src/controllers/prompt.controller.ts @@ -74,6 +74,7 @@ import { getPrimaryForRange, NORMAL_SELECTION_PLUGIN_NAME, SelectionManagerService, + setEndForRange, } from '@univerjs/sheets'; import type { EditorBridgeService, SelectionShape } from '@univerjs/sheets-ui'; import { @@ -149,7 +150,7 @@ export class PromptController extends Disposable { private _previousEditorUnitId: Nullable; - private _executeBlurSetTimeout: number | NodeJS.Timeout = -1; + private _existsSequenceNode = false; constructor( @ICommandService private readonly _commandService: ICommandService, @@ -882,9 +883,11 @@ export class PromptController extends Disposable { this._selectionManagerService.clear(); if (sequenceNodes == null || sequenceNodes.length === 0) { + this._existsSequenceNode = false; bodyList.forEach((body) => (body!.textRuns = [])); } else { // this._lastSequenceNodes = sequenceNodes; + this._existsSequenceNode = true; const { textRuns, refSelections } = this._buildTextRuns(sequenceNodes); bodyList.forEach((body) => (body!.textRuns = textRuns)); @@ -988,7 +991,13 @@ export class PromptController extends Disposable { const gridRange = deserializeRangeWithSheet(token); - const { unitId: refUnitId, sheetName, range } = gridRange; + const { unitId: refUnitId, sheetName, range: rawRange } = gridRange; + + /** + * pro/issues/436 + * When the range is an entire row or column, NaN values need to be corrected. + */ + const range = setEndForRange(rawRange, worksheet.getRowCount(), worksheet.getColumnCount()); if (refUnitId != null && refUnitId.length > 0 && unitId !== refUnitId) { continue; @@ -1381,9 +1390,65 @@ export class PromptController extends Disposable { // } } + /** + * pro/issues/450 + * In range selection mode, certain measures are implemented to ensure that the selection behavior is processed correctly. + */ + private _focusIsOnlyRange(selectionCount: number) { + const currentEditor = this._editorService.getFocusEditor(); + if (!currentEditor) { + return true; + } + + if (!currentEditor.onlyInputRange()) { + return true; + } + + if (this._existsSequenceNode) { + return true; + } + + if (selectionCount > 1 || (this._previousSequenceNodes != null && this._previousSequenceNodes.length > 0)) { + return true; + } + + if (this._previousInsertRefStringIndex != null) { + this._previousInsertRefStringIndex += 1; + } + + return false; + } + + /** + * pro/issues/450 + * In range selection mode, certain measures are implemented to ensure that the selection behavior is processed correctly. + */ + private _resetSequenceNodes(selectionCount: number) { + const currentEditor = this._editorService.getFocusEditor(); + if (!currentEditor) { + return; + } + + if (!currentEditor.onlyInputRange()) { + return; + } + + if (selectionCount > 1) { + return; + } + + if (this._existsSequenceNode) { + this._formulaPromptService.clearSequenceNodes(); + this._previousRangesCount = 0; + this._existsSequenceNode = false; + } + } + private _inertControlSelection(selectionWithStyles: ISelectionWithStyle[]) { const currentSelection = selectionWithStyles[selectionWithStyles.length - 1]; + this._resetSequenceNodes(selectionWithStyles.length); + if ( (selectionWithStyles.length === this._previousRangesCount || this._previousRangesCount === 0) && this._previousSequenceNodes != null @@ -1405,7 +1470,7 @@ export class PromptController extends Disposable { this._previousInsertRefStringIndex = this._currentInsertRefStringIndex; - if (!matchRefDrawToken(char)) { + if (!matchRefDrawToken(char) && this._focusIsOnlyRange(selectionWithStyles.length)) { this._formulaPromptService.insertSequenceString(this._currentInsertRefStringIndex, matchToken.COMMA); insertNodes = this._formulaPromptService.getSequenceNodes(); diff --git a/packages/sheets/src/commands/commands/utils/selection-utils.ts b/packages/sheets/src/commands/commands/utils/selection-utils.ts index 9ee724dcf3..5ffa47175d 100644 --- a/packages/sheets/src/commands/commands/utils/selection-utils.ts +++ b/packages/sheets/src/commands/commands/utils/selection-utils.ts @@ -98,21 +98,45 @@ export function getCellAtRowCol(row: number, col: number, worksheet: Worksheet): return destRange; } +export function setEndForRange(range: IRange, rowCount: number, columnCount: number) { + const { startRow, startColumn, endRow, endColumn } = range; + + if (Number.isNaN(startRow)) { + range.startRow = 0; + } + + if (Number.isNaN(endRow)) { + range.endRow = rowCount; + } + + if (Number.isNaN(startColumn)) { + range.startColumn = 0; + } + + if (Number.isNaN(endColumn)) { + range.endColumn = columnCount; + } + + return range; +} + /** * Get the default primary cell (the most top-left cell) of a range. * @param range * @param worksheet */ export function getPrimaryForRange(range: IRange, worksheet: Worksheet): ISelectionCell { - const mergedRange = worksheet.getMergedCell(range.startRow, range.startColumn); + const startRow = Number.isNaN(range.startRow) ? 0 : range.startRow; + const startColumn = Number.isNaN(range.startColumn) ? 0 : range.startColumn; + const mergedRange = worksheet.getMergedCell(startRow, startColumn); if (!mergedRange) { return { - startRow: range.startRow, - startColumn: range.startColumn, + startRow, + startColumn, endRow: range.startRow, endColumn: range.startColumn, - actualRow: range.startRow, - actualColumn: range.startColumn, + actualRow: startRow, + actualColumn: startColumn, rangeType: RANGE_TYPE.NORMAL, isMerged: false, isMergedMainCell: false, @@ -121,8 +145,8 @@ export function getPrimaryForRange(range: IRange, worksheet: Worksheet): ISelect return { ...mergedRange, - actualRow: range.startRow, - actualColumn: range.startColumn, + actualRow: startRow, + actualColumn: startColumn, rangeType: RANGE_TYPE.NORMAL, isMerged: true, isMergedMainCell: true, diff --git a/packages/sheets/src/index.ts b/packages/sheets/src/index.ts index 1481429538..84e06f14be 100644 --- a/packages/sheets/src/index.ts +++ b/packages/sheets/src/index.ts @@ -42,7 +42,7 @@ export { SELECTION_CONTROL_BORDER_BUFFER_WIDTH, transformCellDataToSelectionData, } from './basics/selection'; -export { alignToMergedCellsBorders, getCellAtRowCol } from './commands/commands/utils/selection-utils'; +export { alignToMergedCellsBorders, getCellAtRowCol, setEndForRange } from './commands/commands/utils/selection-utils'; export { MAX_CELL_PER_SHEET_KEY } from './controllers/config/config'; export { BorderStyleManagerService, type IBorderInfo } from './services/border-style-manager.service'; export { getCurrentSheetDisabled$, SheetEditablePermission, SheetPermissionService } from './services/permission'; diff --git a/packages/ui/src/services/editor/editor.service.ts b/packages/ui/src/services/editor/editor.service.ts index 3beb5c12cf..58012eb5aa 100644 --- a/packages/ui/src/services/editor/editor.service.ts +++ b/packages/ui/src/services/editor/editor.service.ts @@ -307,6 +307,8 @@ export interface IEditorService { setFocusId(id: Nullable): void; getFocusId(): Nullable; + + getFocusEditor(): Readonly>; } export class EditorService extends Disposable implements IEditorService, IDisposable { @@ -367,6 +369,12 @@ export class EditorService extends Disposable implements IEditorService, IDispos return this._focusEditorUnitId; } + getFocusEditor() { + if (this._focusEditorUnitId) { + return this.getEditor(this._focusEditorUnitId); + } + } + isEditor(editorUnitId: string) { return this._editors.has(editorUnitId); }