From d4d9573883ed843d0071db4526e8e2ab8a1ca8ca Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 23 Dec 2025 18:04:13 +0800 Subject: [PATCH 1/7] refactor: when set editCellTrigger keydown input chinese first letter problem #4847 --- packages/vtable-editors/src/input-editor.ts | 9 +- packages/vtable-editors/src/types.ts | 1 + .../vtable/examples/editor/input-editor.ts | 7 ++ packages/vtable/examples/index.html | 7 ++ packages/vtable/src/core/BaseTable.ts | 27 +++-- packages/vtable/src/edit/edit-manager.ts | 41 ++++++- .../vtable/src/edit/editor-input-element.ts | 110 ++++++++++++++++++ .../src/event/listener/container-dom.ts | 44 +++++-- 8 files changed, 212 insertions(+), 34 deletions(-) create mode 100644 packages/vtable/src/edit/editor-input-element.ts diff --git a/packages/vtable-editors/src/input-editor.ts b/packages/vtable-editors/src/input-editor.ts index 735570752e..e8681f037a 100644 --- a/packages/vtable-editors/src/input-editor.ts +++ b/packages/vtable-editors/src/input-editor.ts @@ -14,10 +14,14 @@ export class InputEditor implements IEditor { table?: any; col?: number; row?: number; - + private settedElement: boolean = false; constructor(editorConfig?: InputEditorConfig) { this.editorConfig = editorConfig; } + setElement(element: HTMLInputElement) { + this.element = element; + this.settedElement = true; + } createElement() { const input = document.createElement('input'); @@ -110,9 +114,10 @@ export class InputEditor implements IEditor { onEnd() { // do nothing - if (this.container?.contains(this.element)) { + if (!this.settedElement && this.container?.contains(this.element)) { this.container.removeChild(this.element); } + this.settedElement = false; this.element = undefined; } diff --git a/packages/vtable-editors/src/types.ts b/packages/vtable-editors/src/types.ts index 315cedc967..94ab9d80bd 100644 --- a/packages/vtable-editors/src/types.ts +++ b/packages/vtable-editors/src/types.ts @@ -17,6 +17,7 @@ export interface IEditor { */ onEnd: () => void; getInputElement?: () => HTMLElement; + setElement?: (element: HTMLInputElement) => void; /** * Called when user click somewhere while editor is in edit mode. * diff --git a/packages/vtable/examples/editor/input-editor.ts b/packages/vtable/examples/editor/input-editor.ts index eba310802e..3523bb8056 100644 --- a/packages/vtable/examples/editor/input-editor.ts +++ b/packages/vtable/examples/editor/input-editor.ts @@ -136,6 +136,7 @@ export function createTable() { const option: VTable.ListTableConstructorOptions = { emptyTip: true, container: document.getElementById(CONTAINER_ID), + editCellTrigger: 'keydown', columns: [ { field: 'progress', @@ -170,6 +171,12 @@ export function createTable() { } } ], + hover: { + highlightMode: 'cross' + }, + keyboardOptions: { + selectAllOnCtrlA: true + }, showFrozenIcon: true, //显示VTable内置冻结列图标 widthMode: 'standard', autoFillWidth: true, diff --git a/packages/vtable/examples/index.html b/packages/vtable/examples/index.html index 1ddb20c9dc..93b2eede4b 100644 --- a/packages/vtable/examples/index.html +++ b/packages/vtable/examples/index.html @@ -17,6 +17,13 @@
+
+ +
+
+
+ +
diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 5447dcf27f..34fbc59459 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -503,16 +503,16 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { ? typeof limitMinWidth === 'number' ? limitMinWidth : limitMinWidth - ? 10 - : 0 + ? 10 + : 0 : 10; internalProps.limitMinHeight = limitMinHeight !== null && limitMinHeight !== undefined ? typeof limitMinHeight === 'number' ? limitMinHeight : limitMinHeight - ? 10 - : 0 + ? 10 + : 0 : 10; // 生成scenegraph // this._vDataSet = new DataSet(); @@ -1488,12 +1488,12 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { // : this.defaultColWidth; if (this.isRowHeader(col, 0) || this.isCornerHeader(col, 0)) { return Array.isArray(this.defaultHeaderColWidth) - ? (this.defaultHeaderColWidth[col] ?? this.defaultColWidth) + ? this.defaultHeaderColWidth[col] ?? this.defaultColWidth : this.defaultHeaderColWidth; } else if (this.isRightFrozenColumn(col, this.columnHeaderLevelCount)) { if (this.isPivotTable()) { return Array.isArray(this.defaultHeaderColWidth) - ? (this.defaultHeaderColWidth[this.rowHeaderLevelCount - this.rightFrozenColCount] ?? this.defaultColWidth) + ? this.defaultHeaderColWidth[this.rowHeaderLevelCount - this.rightFrozenColCount] ?? this.defaultColWidth : this.defaultHeaderColWidth; } return this.defaultColWidth; @@ -1504,15 +1504,15 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { getDefaultRowHeight(row: number) { if (this.isColumnHeader(0, row) || this.isCornerHeader(0, row) || this.isSeriesNumberInHeader(0, row)) { return Array.isArray(this.defaultHeaderRowHeight) - ? (this.defaultHeaderRowHeight[row] ?? this.internalProps.defaultRowHeight) + ? this.defaultHeaderRowHeight[row] ?? this.internalProps.defaultRowHeight : this.defaultHeaderRowHeight; } if (this.isBottomFrozenRow(row)) { //底部冻结行默认取用了表头的行高 但针对非表头数据冻结的情况这里可能不妥 return Array.isArray(this.defaultHeaderRowHeight) - ? (this.defaultHeaderRowHeight[ + ? this.defaultHeaderRowHeight[ this.columnHeaderLevelCount > 0 ? this.columnHeaderLevelCount - this.bottomFrozenRowCount : 0 - ] ?? this.internalProps.defaultRowHeight) + ] ?? this.internalProps.defaultRowHeight : this.defaultHeaderRowHeight; } return this.internalProps.defaultRowHeight; @@ -2601,7 +2601,6 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.scenegraph.stage.release(); this.scenegraph.proxy.release(); - internalProps.focusControl.release(); const parentElement = internalProps.element?.parentElement; if (parentElement && !this.options.canvas) { parentElement.removeChild(internalProps.element); @@ -2808,16 +2807,16 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { ? typeof limitMinWidth === 'number' ? limitMinWidth : limitMinWidth - ? 10 - : 0 + ? 10 + : 0 : 10; internalProps.limitMinHeight = limitMinHeight !== null && limitMinHeight !== undefined ? typeof limitMinHeight === 'number' ? limitMinHeight : limitMinHeight - ? 10 - : 0 + ? 10 + : 0 : 10; // 生成scenegraph // this._vDataSet = new DataSet(); diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index c91c28e2f5..3577befbac 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -1,12 +1,15 @@ import type { IEditor, ValidateEnum } from '@visactor/vtable-editors'; import { TABLE_EVENT_TYPE } from '../core/TABLE_EVENT_TYPE'; import type { BaseTableAPI } from '../ts-types/base-table'; -import type { ListTableAPI } from '../ts-types'; +import type { ListTableAPI, ListTableConstructorOptions } from '../ts-types'; import { getCellEventArgsSet } from '../event/util'; import type { SimpleHeaderLayoutMap } from '../layout'; import { isPromise } from '../tools/helper'; import { isValid } from '@visactor/vutils'; import type { IIconGraphicAttribute } from '../scenegraph/graphic/icon'; +import { Rect } from '../tools/Rect'; +import { EditorInputElement } from './editor-input-element'; +import type { BaseTable } from '../core'; export class EditManager { table: BaseTableAPI; @@ -15,17 +18,20 @@ export class EditManager { editCell: { col: number; row: number }; listenersId: number[] = []; beginTriggerEditCellMode: 'doubleclick' | 'click' | 'keydown'; + editInputElement: EditorInputElement | null = null; constructor(table: BaseTableAPI) { this.table = table; - this.bindEvent(); + const { editCellTrigger = 'doubleclick' } = table.options as ListTableConstructorOptions; + if (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) { + this.editInputElement = new EditorInputElement(table as BaseTable, table.getElement()); + } + this.bindEvent(editCellTrigger as string); } - bindEvent() { + bindEvent(editCellTrigger: string) { // const handler = this.table.internalProps.handler; const table = this.table as ListTableAPI; const doubleClickEventId = table.on(TABLE_EVENT_TYPE.DBLCLICK_CELL, e => { - const { editCellTrigger = 'doubleclick' } = table.options; - if (!editCellTrigger.includes('doubleclick')) { return; } @@ -62,6 +68,22 @@ export class EditManager { this.beginTriggerEditCellMode = 'click'; const { col, row } = e; this.startEditCell(col, row); + } else if ( + editCellTrigger === 'keydown' || + (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown')) + ) { + const { col, row } = e; + this.beginTriggerEditCellMode = 'keydown'; + const rect = this.table.getCellRangeRelativeRect(this.table.getCellRange(col, row)); + this.editInputElement.setRect( + new Rect(rect.left + table.scrollLeft, rect.top + table.scrollTop, rect.width, rect.height), + '' + ); + this.editInputElement.hide(); + this.editInputElement.focus(); + // debugger; + e.event.preventDefault(); + e.event.stopPropagation(); } }); @@ -77,12 +99,13 @@ export class EditManager { // }); } - startEditCell(col: number, row: number, value?: string | number) { + startEditCell(col: number, row: number, value?: string | number, editElement?: HTMLInputElement) { if (this.editingEditor) { return; } const editor = (this.table as ListTableAPI).getEditor(col, row); if (editor) { + editElement && editor.setElement(editElement); // //自定义内容单元格不允许编辑 // if (this.table.getCustomRender(col, row) || this.table.getCustomLayout(col, row)) { // console.warn("VTable Warn: cell has config custom render or layout, can't be edited"); @@ -251,6 +274,9 @@ export class EditManager { this.editingEditor = null; this.isValidatingValue = false; this.beginTriggerEditCellMode = null; + if (this.editInputElement) { + this.editInputElement.hide(); + } } cancelEdit() { @@ -266,6 +292,9 @@ export class EditManager { this.listenersId.forEach(id => { this.table.off(id); }); + if (this.editInputElement) { + this.editInputElement.release(); + } } } diff --git a/packages/vtable/src/edit/editor-input-element.ts b/packages/vtable/src/edit/editor-input-element.ts new file mode 100644 index 0000000000..84e81cc032 --- /dev/null +++ b/packages/vtable/src/edit/editor-input-element.ts @@ -0,0 +1,110 @@ +import type { BaseTable } from '../core'; +import type { Rect } from '../tools/Rect'; +import { Env } from '../tools/env'; +/** + * editCellTrigger === 'keydown' 时,使用这个类来处理键盘事件。否则当键盘按下再创建input就晚了,中文输入法第一个字符会被当做英文字符 + */ +export class EditorInputElement extends EventTarget { + private _container: HTMLDivElement; + private _table: BaseTable; + private _input: HTMLInputElement; + constructor(table: BaseTable, parentElement: HTMLElement) { + super(); + this._table = table; + if (Env.mode === 'node') { + return; + } + const div = document.createElement('div'); //再加一层 C360插件逻辑中用的window.getSelection()来判断的滚动 + // div.style.position = 'fixed';//定位不能使用fixed 在父级transform非none的时候 都会有问题 + div.style.opacity = '0'; + div.style.pointerEvents = 'none'; + div.id = 'vtable_editor_input_element'; + div.style.position = 'absolute'; + // div.classList.add('input-container'); + const input = (this._input = document.createElement('input')); + div.appendChild(input); + input.style.position = 'relative'; + input.style.padding = '0'; + input.style.margin = '0'; + input.style.float = 'none'; + input.style.boxSizing = 'border-box'; + // input.classList.add('table-focus-control'); + parentElement.appendChild(div); + this._container = div; + this.bindEvent(); + } + + show(): void { + this._container.style.pointerEvents = 'auto'; + this._container.style.opacity = '1'; + // this._input.style.opacity = '1'; + // this._input.style.zIndex = '99999999'; + // this._input.style.pointerEvents = 'auto'; + // this._input.readOnly = false; + } + hide(): void { + this._container.style.pointerEvents = 'none'; + this._container.style.opacity = '0'; + // this._input.style.opacity = '0'; + // this._input.style.zIndex = '0'; + // this._input.style.pointerEvents = 'none'; + // this._input.readOnly = true; + } + bindEvent(): void { + this._input.addEventListener('keydown', (e: KeyboardEvent) => { + if ( + e.key === 'Enter' || + e.key === 'Escape' || + e.key === 'Tab' || + e.key === 'ArrowUp' || + e.key === 'ArrowDown' || + e.key === 'ArrowLeft' || + e.key === 'ArrowRight' || + e.key === 'Backspace' || + e.key === 'Delete' || + e.key === 'Home' || + e.key === 'End' || + e.key === 'PageUp' || + e.key === 'PageDown' || + e.key === 'Insert' + ) { + this._input.blur(); + } else if (!(e.ctrlKey || e.metaKey)) { + if (this._table.editorManager.editingEditor) { + return; + } + const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 + if (e.key.match(allowedKeys)) { + this.show(); + this._table.editorManager.startEditCell( + this._table.stateManager.select.cellPos.col, + this._table.stateManager.select.cellPos.row, + undefined, + this._input + ); + } + } + }); + } + + focus(): void { + this._input.focus({ preventScroll: true }); + } + + setRect(rect: Rect, value: string): void { + const input = this._input; + input.value = value; + const top = rect.top - this._table.scrollTop; + const left = rect.left - this._table.scrollLeft; + input.style.top = `${top.toFixed()}px`; + input.style.left = `${left.toFixed()}px`; + input.style.width = `${rect.width.toFixed()}px`; + input.style.height = `${rect.height.toFixed()}px`; + } + get input(): HTMLInputElement { + return this._input; + } + release(): void { + this._container.parentElement?.removeChild(this._container); + } +} diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index d9563cf4d8..8be2d9ad0f 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -22,7 +22,25 @@ export function bindContainerDomListener(eventManager: EventManager) { // } // }); - handler.on(table.getElement(), 'blur', (e: MouseEvent) => { + handler.on(table.getElement(), 'blur', (e: FocusEvent) => { + // 检查焦点是否转移到了表格内部的元素(如 editInputElement) + // 如果是,则不处理 blur 事件,避免在编辑时触发不必要的逻辑 + const relatedTarget = e.relatedTarget as HTMLElement; + if (relatedTarget) { + // 检查是否是 editInputElement 的 input + const editInputElement = (table as ListTableAPI).editorManager?.editInputElement; + if (editInputElement && editInputElement.input === relatedTarget) { + return; + } + // 检查是否是编辑器内部的 input + const editingEditor = (table as ListTableAPI).editorManager?.editingEditor; + if (editingEditor && typeof editingEditor.getInputElement === 'function') { + const editorInput = editingEditor.getInputElement(); + if (editorInput === relatedTarget) { + return; + } + } + } eventManager.dealTableHover(); // eventManager.dealTableSelect(); }); @@ -217,20 +235,22 @@ export function bindContainerDomListener(eventManager: EventManager) { } } } else if (!(e.ctrlKey || e.metaKey)) { - const editCellTrigger = (table.options as ListTableConstructorOptions).editCellTrigger; - if ( - (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) && - !table.editorManager?.editingEditor - ) { - const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 - if (e.key.match(allowedKeys)) { - table.editorManager && (table.editorManager.beginTriggerEditCellMode = 'keydown'); - table.editorManager?.startEditCell(stateManager.select.cellPos.col, stateManager.select.cellPos.row, ''); - } - } + // 以下逻辑已废弃,挪到了editor-input-element.ts中处理 + // const editCellTrigger = (table.options as ListTableConstructorOptions).editCellTrigger; + // if ( + // (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) && + // !table.editorManager?.editingEditor + // ) { + // const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 + // if (e.key.match(allowedKeys)) { + // table.editorManager && (table.editorManager.beginTriggerEditCellMode = 'keydown'); + // table.editorManager?.startEditCell(stateManager.select.cellPos.col, stateManager.select.cellPos.row, ''); + // } + // } } handleKeydownListener(e); }); + /** * 处理主动注册的keydown事件 * @param e From a541916f3fa352d51417c50700ea391476615939 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 23 Dec 2025 18:04:45 +0800 Subject: [PATCH 2/7] docs: update changlog of rush --- ...or-keydownTriggerEditChinese_2025-12-23-10-04.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-23-10-04.json diff --git a/common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-23-10-04.json b/common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-23-10-04.json new file mode 100644 index 0000000000..8eb932543f --- /dev/null +++ b/common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-23-10-04.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: when set editCellTrigger keydown input chinese first letter problem #4847\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From b7f2894537b47caf45dc434ba40d3e146ae3e80b Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 24 Dec 2025 16:28:25 +0800 Subject: [PATCH 3/7] fix: editor support keydown chinese #4847 --- packages/vtable-editors/src/input-editor.ts | 57 ++++++--- packages/vtable-editors/src/types.ts | 13 +++ .../examples/editor/custom-date-editor.ts | 1 + .../vtable/examples/editor/input-editor.ts | 27 ++++- packages/vtable/src/ListTable.ts | 18 ++- packages/vtable/src/PivotTable.ts | 24 ++-- packages/vtable/src/edit/edit-manager.ts | 81 ++++++++++--- .../vtable/src/edit/editor-input-element.ts | 110 ------------------ .../src/event/listener/container-dom.ts | 63 ++++++---- 9 files changed, 210 insertions(+), 184 deletions(-) delete mode 100644 packages/vtable/src/edit/editor-input-element.ts diff --git a/packages/vtable-editors/src/input-editor.ts b/packages/vtable-editors/src/input-editor.ts index e8681f037a..f5e9dd0f28 100644 --- a/packages/vtable-editors/src/input-editor.ts +++ b/packages/vtable-editors/src/input-editor.ts @@ -1,4 +1,4 @@ -import type { CellAddress, EditContext, IEditor, RectProps } from './types'; +import type { CellAddress, EditContext, IEditor, PrepareEditContext, RectProps } from './types'; import type { ValidateEnum } from './types'; export interface InputEditorConfig { @@ -14,13 +14,11 @@ export class InputEditor implements IEditor { table?: any; col?: number; row?: number; - private settedElement: boolean = false; constructor(editorConfig?: InputEditorConfig) { this.editorConfig = editorConfig; } - setElement(element: HTMLInputElement) { - this.element = element; - this.settedElement = true; + getInputElement(): HTMLInputElement { + return this.element; } createElement() { @@ -44,9 +42,15 @@ export class InputEditor implements IEditor { input.style.outline = 'none'; }); - input.addEventListener('blur', () => { + input.addEventListener('blur', e => { input.style.borderColor = '#d9d9d9'; // input.style.boxShadow = 'none'; + if (this.table && this.element.style.opacity === '0') { + const selectCell = this.table.stateManager.select.cellPos; + if (selectCell.col !== this.col || selectCell.row !== this.row) { + this.onEnd(); + } + } }); // #endregion this.element = input; @@ -73,7 +77,31 @@ export class InputEditor implements IEditor { getValue() { return this.element.value; } - + /** + * 如果表格编辑时机配置editCellTrigger为keydown,则需要调用prepareEdit来准备编辑环境,否则中文输入法第一个字符会被当做英文字符 + * @param param0 + */ + prepareEdit({ referencePosition, container, table, col, row }: PrepareEditContext) { + this.container = container; + this.table = table; + this.col = col; + this.row = row; + const selectCell = this.table.stateManager.select.cellPos; + if (selectCell.col !== this.col || selectCell.row !== this.row) { + return; + } + if (!this.element) { + this.createElement(); + } + this.element.style.opacity = '0'; + //这个pointerEvents = 'none'很重要,如果没有的话会引起vtable.getElement()元素和这里的element元素的focus和blur的切换, + //也会引起mouseleave_table mouseleave_cell和mouseenter的切换 + this.element.style.pointerEvents = 'none'; + if (referencePosition?.rect) { + this.adjustPosition(referencePosition.rect); + } + this.element.focus(); + } onStart({ value, referencePosition, container, endEdit, table, col, row }: EditContext) { this.container = container; this.successCallback = endEdit; @@ -82,14 +110,16 @@ export class InputEditor implements IEditor { this.row = row; if (!this.element) { this.createElement(); - - if (value !== undefined && value !== null) { - this.setValue(value); - } if (referencePosition?.rect) { this.adjustPosition(referencePosition.rect); } } + if (value !== undefined && value !== null) { + this.setValue(value); + } + //防止调用过prepareEdit 后,元素的显示和可操作性被影响 + this.element.style.opacity = '1'; + this.element.style.pointerEvents = 'auto'; this.element.focus(); // do nothing } @@ -114,11 +144,10 @@ export class InputEditor implements IEditor { onEnd() { // do nothing - if (!this.settedElement && this.container?.contains(this.element)) { + if (this.container?.contains(this.element)) { this.container.removeChild(this.element); + this.element = undefined; } - this.settedElement = false; - this.element = undefined; } isEditorElement(target: HTMLElement) { diff --git a/packages/vtable-editors/src/types.ts b/packages/vtable-editors/src/types.ts index 94ab9d80bd..3937f9b7b0 100644 --- a/packages/vtable-editors/src/types.ts +++ b/packages/vtable-editors/src/types.ts @@ -47,6 +47,11 @@ export interface IEditor { * Expected to return the current value of the cell. */ getValue: () => V; + /** + * If the table editing trigger is configured as keydown, you need to call prepareEdit to prepare the editing environment, otherwise the first character of the Chinese input method will be treated as an English character. + * @param param0 + */ + prepareEdit?: (context: PrepareEditContext) => void; /** * Called when cell enter edit mode. * @deprecated use `onStart` instead. @@ -69,6 +74,7 @@ export interface IEditor { * @deprecated callback is provided as `endEdit` in `EditContext`, use `onStart` instead. */ bindSuccessCallback?: (callback: () => void) => void; + adjustPosition?: (rect: RectProps) => void; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -97,6 +103,13 @@ export interface EditContext { row: number; } +export interface PrepareEditContext { + container: HTMLElement; + table: T; + col: number; + row: number; + referencePosition: ReferencePosition; +} export interface RectProps { left: number; top: number; diff --git a/packages/vtable/examples/editor/custom-date-editor.ts b/packages/vtable/examples/editor/custom-date-editor.ts index ad5d1ccf34..ba3d302d6c 100644 --- a/packages/vtable/examples/editor/custom-date-editor.ts +++ b/packages/vtable/examples/editor/custom-date-editor.ts @@ -309,6 +309,7 @@ export function createTable() { container: document.getElementById(CONTAINER_ID), records, columns, + editCellTrigger: ['keydown', 'doubleclick'], keyboardOptions: { copySelected: true, pasteValueToCell: true, diff --git a/packages/vtable/examples/editor/input-editor.ts b/packages/vtable/examples/editor/input-editor.ts index 3523bb8056..a2acef730a 100644 --- a/packages/vtable/examples/editor/input-editor.ts +++ b/packages/vtable/examples/editor/input-editor.ts @@ -1,6 +1,7 @@ import * as VTable from '../../src'; import { InputEditor } from '@visactor/vtable-editors'; import { bindDebugTool } from '../../src/scenegraph/debug-tool'; +import { TABLE_EVENT_TYPE } from '../../src'; const ListTable = VTable.ListTable; const CONTAINER_ID = 'vTable'; const input_editor = new InputEditor({}); @@ -192,10 +193,30 @@ export function createTable() { field: 'progress', order: 'desc' }); - - instance.on('change_cell_value', arg => { - console.log(arg); + // instance.on(TABLE_EVENT_TYPE.CLICK_CELL, e => { + // console.log('click-cell', e); + // }); + // instance.on(TABLE_EVENT_TYPE.SELECTED_CHANGED, e => { + // console.log('selected_changed', e); + // }); + instance.on(TABLE_EVENT_TYPE.DRAG_SELECT_END, e => { + console.log('drag_select_end', e); }); + // instance.on('change_cell_value', arg => { + // console.log('.....change_cell_value'); + // }); + // instance.on('mouseleave_cell', arg => { + // console.log('.....mouseleave_cell'); + // }); + // instance.on('mouseenter_cell', arg => { + // console.log('.....mouseenter_cell'); + // }); + // instance.on('mouseleave_table', arg => { + // console.log('.....mouseleave_table'); + // }); + // instance.on('mouseenter_table', arg => { + // console.log('.....mouseenter_table'); + // }); // bindDebugTool(instance.scenegraph.stage as any, { // customGrapicKeys: ['role', '_updateTag'] diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 4224a65291..5e84de3342 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1532,12 +1532,16 @@ export class ListTable extends BaseTable implements ListTableAPI { } /** 获取单元格对应的编辑器 */ getEditor(col: number, row: number) { + const lastSelectedCellEditor = this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`]; + if (lastSelectedCellEditor) { + return lastSelectedCellEditor; + } const define = this.getBodyColumnDefine(col, row); - let editorDefine = this.isHeader(col, row) + let editor = this.isHeader(col, row) ? (define as ColumnDefine)?.headerEditor ?? this.options.headerEditor : (define as ColumnDefine)?.editor ?? this.options.editor; - if (typeof editorDefine === 'function') { + if (typeof editor === 'function') { const arg = { col, row, @@ -1545,12 +1549,14 @@ export class ListTable extends BaseTable implements ListTableAPI { value: this.getCellValue(col, row) || '', table: this }; - editorDefine = (editorDefine as Function)(arg); + editor = (editor as Function)(arg); } - if (typeof editorDefine === 'string') { - return editors.get(editorDefine); + if (typeof editor === 'string') { + editor = editors.get(editor); } - return editorDefine as IEditor; + this.editorManager.cacheLastSelectedCellEditor = {}; + this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`] = editor as IEditor; + return editor as IEditor; } /** 检查单元格是否定义过编辑器 不管编辑器是否有效 只要有定义就返回true */ isHasEditorDefine(col: number, row: number) { diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 8a90ce43ca..753955eba0 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -1755,18 +1755,22 @@ export class PivotTable extends BaseTable implements PivotTableAPI { } /** 获取单元格对应的编辑器 */ getEditor(col: number, row: number) { - let editorDefine; + const lastSelectedCellEditor = this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`]; + if (lastSelectedCellEditor) { + return lastSelectedCellEditor; + } + let editor; if (this.isCornerHeader(col, row)) { const define = this.getHeaderDefine(col, row); - editorDefine = (define as ColumnDefine)?.headerEditor ?? this.options.headerEditor; + editor = (define as ColumnDefine)?.headerEditor ?? this.options.headerEditor; } else if (this.isHeader(col, row)) { const define = this.getHeaderDefine(col, row); - editorDefine = (define as ColumnDefine)?.headerEditor ?? this.options.headerEditor; + editor = (define as ColumnDefine)?.headerEditor ?? this.options.headerEditor; } else { const define = this.getBodyColumnDefine(col, row); - editorDefine = (define as ColumnDefine)?.editor ?? this.options.editor; + editor = (define as ColumnDefine)?.editor ?? this.options.editor; } - if (typeof editorDefine === 'function') { + if (typeof editor === 'function') { const arg = { col, row, @@ -1774,12 +1778,14 @@ export class PivotTable extends BaseTable implements PivotTableAPI { value: this.getCellValue(col, row) || '', table: this }; - editorDefine = (editorDefine as Function)(arg); + editor = (editor as Function)(arg); } - if (typeof editorDefine === 'string') { - return editors.get(editorDefine); + if (typeof editor === 'string') { + editor = editors.get(editor); } - return editorDefine as IEditor; + this.editorManager.cacheLastSelectedCellEditor = {}; + this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`] = editor as IEditor; + return editor as IEditor; } /** 检查单元格是否定义过编辑器 不管编辑器是否有效 只要有定义就返回true */ isHasEditorDefine(col: number, row: number) { diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index 3577befbac..be96a1dd61 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -7,9 +7,6 @@ import type { SimpleHeaderLayoutMap } from '../layout'; import { isPromise } from '../tools/helper'; import { isValid } from '@visactor/vutils'; import type { IIconGraphicAttribute } from '../scenegraph/graphic/icon'; -import { Rect } from '../tools/Rect'; -import { EditorInputElement } from './editor-input-element'; -import type { BaseTable } from '../core'; export class EditManager { table: BaseTableAPI; @@ -18,13 +15,10 @@ export class EditManager { editCell: { col: number; row: number }; listenersId: number[] = []; beginTriggerEditCellMode: 'doubleclick' | 'click' | 'keydown'; - editInputElement: EditorInputElement | null = null; + cacheLastSelectedCellEditor: Record = {}; constructor(table: BaseTableAPI) { this.table = table; const { editCellTrigger = 'doubleclick' } = table.options as ListTableConstructorOptions; - if (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) { - this.editInputElement = new EditorInputElement(table as BaseTable, table.getElement()); - } this.bindEvent(editCellTrigger as string); } @@ -71,23 +65,73 @@ export class EditManager { } else if ( editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown')) + ) { + console.log('click-cell', e); + // const { col, row } = e; + // this.beginTriggerEditCellMode = 'keydown'; + // const rect = this.table.getCellRangeRelativeRect(this.table.getCellRange(col, row)); + // const referencePosition = { rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height } }; + // // adjust last col&row, same as packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts getCellSizeForDraw + // if (col === this.table.colCount - 1) { + // referencePosition.rect.width = rect.width - 1; + // } else { + // referencePosition.rect.width = rect.width + 1; // 这里的1应该根据单元格的borderWidth来定; + // } + // if (row === this.table.rowCount - 1) { + // referencePosition.rect.height = rect.height - 1; + // } else { + // referencePosition.rect.height = rect.height + 1; // 这里的1应该根据单元格的borderWidth来定; + // } + // const editor = (this.table as ListTableAPI).getEditor(col, row); + // console.log('prepareEdit click-cell'); + // //需要准备编辑环境,等待输入。否则中文输入法第一个字符会被当做英文字符 + // editor.prepareEdit?.({ referencePosition, container: this.table.getElement(), table: this.table, col, row }); + } + }); + const selectedChangedEventId = table.on(TABLE_EVENT_TYPE.SELECTED_CHANGED, e => { + const selectedRanges = table.stateManager.select.ranges; + const justOneCellSelected = + selectedRanges.length === 1 && + selectedRanges[0].start.col === selectedRanges[0].end.col && + selectedRanges[0].start.row === selectedRanges[0].end.row; + if ( + justOneCellSelected && + (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) ) { const { col, row } = e; this.beginTriggerEditCellMode = 'keydown'; const rect = this.table.getCellRangeRelativeRect(this.table.getCellRange(col, row)); - this.editInputElement.setRect( - new Rect(rect.left + table.scrollLeft, rect.top + table.scrollTop, rect.width, rect.height), - '' - ); - this.editInputElement.hide(); - this.editInputElement.focus(); - // debugger; - e.event.preventDefault(); - e.event.stopPropagation(); + const referencePosition = { rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height } }; + // adjust last col&row, same as packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts getCellSizeForDraw + if (col === this.table.colCount - 1) { + referencePosition.rect.width = rect.width - 1; + } else { + referencePosition.rect.width = rect.width + 1; // 这里的1应该根据单元格的borderWidth来定; + } + if (row === this.table.rowCount - 1) { + referencePosition.rect.height = rect.height - 1; + } else { + referencePosition.rect.height = rect.height + 1; // 这里的1应该根据单元格的borderWidth来定; + } + const editor = (this.table as ListTableAPI).getEditor(col, row); + + setTimeout(() => { + // 为什么要加延时:因为这个SELECTED_CHANGED事件是pointerdown过来的, + // 如果这里不加延时,会导致鼠标抬起pointerup的时候将table.getElement()元素设置成焦点,从而导致编辑器失去焦点(因为prepareEdit只是将editor的element设置pointerEvents为none) + if (this.editingEditor !== editor) { + // 判断当前编辑器如果是当前需要准备的编辑器,则不进行准备编辑。这个是为了container-dom文件moveEditCellOnArrowKeys前后逻辑问题,前面有个selectCell会触发这个事件,后面有startEdit了,所以这个prepare就没必要了,触发的话反而有问题 + editor.prepareEdit?.({ + referencePosition, + container: this.table.getElement(), + table: this.table, + col, + row + }); + } + }, 10); } }); - - this.listenersId.push(doubleClickEventId, clickEventId); + this.listenersId.push(doubleClickEventId, clickEventId, selectedChangedEventId); // handler.on(this.table.getElement(), 'wheel', (e: WheelEvent) => { // this.completeEdit(); @@ -196,6 +240,7 @@ export class EditManager { if (this.isValidatingValue) { return false; } + this.cacheLastSelectedCellEditor = {}; const target = e?.target as HTMLElement | undefined; const { editingEditor: editor } = this; diff --git a/packages/vtable/src/edit/editor-input-element.ts b/packages/vtable/src/edit/editor-input-element.ts deleted file mode 100644 index 84e81cc032..0000000000 --- a/packages/vtable/src/edit/editor-input-element.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { BaseTable } from '../core'; -import type { Rect } from '../tools/Rect'; -import { Env } from '../tools/env'; -/** - * editCellTrigger === 'keydown' 时,使用这个类来处理键盘事件。否则当键盘按下再创建input就晚了,中文输入法第一个字符会被当做英文字符 - */ -export class EditorInputElement extends EventTarget { - private _container: HTMLDivElement; - private _table: BaseTable; - private _input: HTMLInputElement; - constructor(table: BaseTable, parentElement: HTMLElement) { - super(); - this._table = table; - if (Env.mode === 'node') { - return; - } - const div = document.createElement('div'); //再加一层 C360插件逻辑中用的window.getSelection()来判断的滚动 - // div.style.position = 'fixed';//定位不能使用fixed 在父级transform非none的时候 都会有问题 - div.style.opacity = '0'; - div.style.pointerEvents = 'none'; - div.id = 'vtable_editor_input_element'; - div.style.position = 'absolute'; - // div.classList.add('input-container'); - const input = (this._input = document.createElement('input')); - div.appendChild(input); - input.style.position = 'relative'; - input.style.padding = '0'; - input.style.margin = '0'; - input.style.float = 'none'; - input.style.boxSizing = 'border-box'; - // input.classList.add('table-focus-control'); - parentElement.appendChild(div); - this._container = div; - this.bindEvent(); - } - - show(): void { - this._container.style.pointerEvents = 'auto'; - this._container.style.opacity = '1'; - // this._input.style.opacity = '1'; - // this._input.style.zIndex = '99999999'; - // this._input.style.pointerEvents = 'auto'; - // this._input.readOnly = false; - } - hide(): void { - this._container.style.pointerEvents = 'none'; - this._container.style.opacity = '0'; - // this._input.style.opacity = '0'; - // this._input.style.zIndex = '0'; - // this._input.style.pointerEvents = 'none'; - // this._input.readOnly = true; - } - bindEvent(): void { - this._input.addEventListener('keydown', (e: KeyboardEvent) => { - if ( - e.key === 'Enter' || - e.key === 'Escape' || - e.key === 'Tab' || - e.key === 'ArrowUp' || - e.key === 'ArrowDown' || - e.key === 'ArrowLeft' || - e.key === 'ArrowRight' || - e.key === 'Backspace' || - e.key === 'Delete' || - e.key === 'Home' || - e.key === 'End' || - e.key === 'PageUp' || - e.key === 'PageDown' || - e.key === 'Insert' - ) { - this._input.blur(); - } else if (!(e.ctrlKey || e.metaKey)) { - if (this._table.editorManager.editingEditor) { - return; - } - const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 - if (e.key.match(allowedKeys)) { - this.show(); - this._table.editorManager.startEditCell( - this._table.stateManager.select.cellPos.col, - this._table.stateManager.select.cellPos.row, - undefined, - this._input - ); - } - } - }); - } - - focus(): void { - this._input.focus({ preventScroll: true }); - } - - setRect(rect: Rect, value: string): void { - const input = this._input; - input.value = value; - const top = rect.top - this._table.scrollTop; - const left = rect.left - this._table.scrollLeft; - input.style.top = `${top.toFixed()}px`; - input.style.left = `${left.toFixed()}px`; - input.style.width = `${rect.width.toFixed()}px`; - input.style.height = `${rect.height.toFixed()}px`; - } - get input(): HTMLInputElement { - return this._input; - } - release(): void { - this._container.parentElement?.removeChild(this._container); - } -} diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index 8be2d9ad0f..1bc1244579 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -27,26 +27,40 @@ export function bindContainerDomListener(eventManager: EventManager) { // 如果是,则不处理 blur 事件,避免在编辑时触发不必要的逻辑 const relatedTarget = e.relatedTarget as HTMLElement; if (relatedTarget) { - // 检查是否是 editInputElement 的 input - const editInputElement = (table as ListTableAPI).editorManager?.editInputElement; - if (editInputElement && editInputElement.input === relatedTarget) { - return; - } // 检查是否是编辑器内部的 input - const editingEditor = (table as ListTableAPI).editorManager?.editingEditor; - if (editingEditor && typeof editingEditor.getInputElement === 'function') { - const editorInput = editingEditor.getInputElement(); - if (editorInput === relatedTarget) { - return; - } + const selectedRanges = table.stateManager.select.ranges; + const justOneCellSelected = + selectedRanges.length === 1 && + selectedRanges[0].start.col === selectedRanges[0].end.col && + selectedRanges[0].start.row === selectedRanges[0].end.row; + const editor = + justOneCellSelected && + (table as ListTableAPI).getEditor(table.stateManager.select.cellPos.col, table.stateManager.select.cellPos.row); + const editorInput = editor?.getInputElement?.(); + if (editorInput === relatedTarget) { + return; } } + eventManager.dealTableHover(); // eventManager.dealTableSelect(); }); // 监听键盘事件 - handler.on(table.getElement(), 'keydown', (e: KeyboardEvent) => { + handler.on(document.body, 'keydown', (e: KeyboardEvent) => { + const selectedRanges = table.stateManager.select.ranges; + const justOneCellSelected = + selectedRanges.length === 1 && + selectedRanges[0].start.col === selectedRanges[0].end.col && + selectedRanges[0].start.row === selectedRanges[0].end.row; + const editor = + justOneCellSelected && + (table as ListTableAPI).getEditor(table.stateManager.select.cellPos.col, table.stateManager.select.cellPos.row); + const editorInput = editor?.getInputElement?.(); + + if (e.target !== table.getElement() && e.target !== editorInput) { + return; + } // 键盘按下事件 内部逻辑处理前 const beforeKeydownEvent: KeydownEvent = { keyCode: e.keyCode ?? e.which, @@ -235,18 +249,19 @@ export function bindContainerDomListener(eventManager: EventManager) { } } } else if (!(e.ctrlKey || e.metaKey)) { - // 以下逻辑已废弃,挪到了editor-input-element.ts中处理 - // const editCellTrigger = (table.options as ListTableConstructorOptions).editCellTrigger; - // if ( - // (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) && - // !table.editorManager?.editingEditor - // ) { - // const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 - // if (e.key.match(allowedKeys)) { - // table.editorManager && (table.editorManager.beginTriggerEditCellMode = 'keydown'); - // table.editorManager?.startEditCell(stateManager.select.cellPos.col, stateManager.select.cellPos.row, ''); - // } - // } + const editCellTrigger = (table.options as ListTableConstructorOptions).editCellTrigger; + if (justOneCellSelected) { + if ( + (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) && + !table.editorManager?.editingEditor + ) { + const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 + if (e.key.match(allowedKeys)) { + table.editorManager && (table.editorManager.beginTriggerEditCellMode = 'keydown'); + table.editorManager?.startEditCell(stateManager.select.cellPos.col, stateManager.select.cellPos.row, ''); + } + } + } } handleKeydownListener(e); }); From a86c6f9b8cb574da31b657ff4f52ca91aae15248 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 24 Dec 2025 16:29:43 +0800 Subject: [PATCH 4/7] docs: update changlog of rush --- ...or-keydownTriggerEditChinese_2025-12-24-08-29.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-24-08-29.json diff --git a/common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-24-08-29.json b/common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-24-08-29.json new file mode 100644 index 0000000000..ddb590af47 --- /dev/null +++ b/common/changes/@visactor/vtable/refactor-keydownTriggerEditChinese_2025-12-24-08-29.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: editor support keydown chinese #4847\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 7e97cefac997b888d0bb28e9e136ba5672be24a8 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 24 Dec 2025 17:48:23 +0800 Subject: [PATCH 5/7] fix: editor support keydown chinese #4847 --- packages/vtable/src/edit/edit-manager.ts | 2 - .../src/event/listener/container-dom.ts | 39 ++++++++----------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index be96a1dd61..52c4ead75b 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -66,7 +66,6 @@ export class EditManager { editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown')) ) { - console.log('click-cell', e); // const { col, row } = e; // this.beginTriggerEditCellMode = 'keydown'; // const rect = this.table.getCellRangeRelativeRect(this.table.getCellRange(col, row)); @@ -114,7 +113,6 @@ export class EditManager { referencePosition.rect.height = rect.height + 1; // 这里的1应该根据单元格的borderWidth来定; } const editor = (this.table as ListTableAPI).getEditor(col, row); - setTimeout(() => { // 为什么要加延时:因为这个SELECTED_CHANGED事件是pointerdown过来的, // 如果这里不加延时,会导致鼠标抬起pointerup的时候将table.getElement()元素设置成焦点,从而导致编辑器失去焦点(因为prepareEdit只是将editor的element设置pointerEvents为none) diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index 1bc1244579..f96306be58 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -47,20 +47,7 @@ export function bindContainerDomListener(eventManager: EventManager) { }); // 监听键盘事件 - handler.on(document.body, 'keydown', (e: KeyboardEvent) => { - const selectedRanges = table.stateManager.select.ranges; - const justOneCellSelected = - selectedRanges.length === 1 && - selectedRanges[0].start.col === selectedRanges[0].end.col && - selectedRanges[0].start.row === selectedRanges[0].end.row; - const editor = - justOneCellSelected && - (table as ListTableAPI).getEditor(table.stateManager.select.cellPos.col, table.stateManager.select.cellPos.row); - const editorInput = editor?.getInputElement?.(); - - if (e.target !== table.getElement() && e.target !== editorInput) { - return; - } + handler.on(table.getElement(), 'keydown', (e: KeyboardEvent) => { // 键盘按下事件 内部逻辑处理前 const beforeKeydownEvent: KeydownEvent = { keyCode: e.keyCode ?? e.which, @@ -149,15 +136,14 @@ export function bindContainerDomListener(eventManager: EventManager) { if (isCellDisableSelect(table, targetCol, targetRow)) { return; } + const isEditingCell = !!(table as ListTableAPI).editorManager?.editingEditor; + // 下面这句completeEdit代码和selectCell代码顺序很重要,不能颠倒,否则会导致编辑器失去焦点(selectCell会触发到edit-manager的selected_changed事件,getEditor会创建editor实例并缓存,completeEdit会清空缓存) + (table as ListTableAPI).editorManager?.completeEdit(); + table.getElement().focus(); const enableShiftSelectMode = table.options.keyboardOptions?.shiftMultiSelect ?? true; table.selectCell(targetCol, targetRow, e.shiftKey && enableShiftSelectMode); - if ( - (table.options.keyboardOptions?.moveEditCellOnArrowKeys ?? false) && - (table as ListTableAPI).editorManager?.editingEditor - ) { + if ((table.options.keyboardOptions?.moveEditCellOnArrowKeys ?? false) && isEditingCell) { // 开启了方向键切换编辑单元格 并且当前已经在编辑状态下 切换到下一个需先退出再进入下个单元格的编辑 - (table as ListTableAPI).editorManager?.completeEdit(); - table.getElement().focus(); if ((table as ListTableAPI).getEditor(targetCol, targetRow)) { (table as ListTableAPI).editorManager?.startEditCell(targetCol, targetRow); } @@ -238,10 +224,12 @@ export function bindContainerDomListener(eventManager: EventManager) { if (isCellDisableSelect(table, targetCol, targetRow)) { return; } + const isEditingCell = !!(table as ListTableAPI).editorManager?.editingEditor; + // 下面这句completeEdit代码和selectCell代码顺序很重要,不能颠倒,否则会导致编辑器失去焦点(selectCell会触发到edit-manager的selected_changed事件,getEditor会创建editor实例并缓存,completeEdit会清空缓存) + (table as ListTableAPI).editorManager?.completeEdit(); + table.getElement().focus(); table.selectCell(targetCol, targetRow); - if ((table as ListTableAPI).editorManager?.editingEditor) { - (table as ListTableAPI).editorManager?.completeEdit(); - table.getElement().focus(); + if (isEditingCell) { if ((table as ListTableAPI).getEditor(targetCol, targetRow)) { (table as ListTableAPI).editorManager?.startEditCell(targetCol, targetRow); } @@ -250,6 +238,11 @@ export function bindContainerDomListener(eventManager: EventManager) { } } else if (!(e.ctrlKey || e.metaKey)) { const editCellTrigger = (table.options as ListTableConstructorOptions).editCellTrigger; + const selectedRanges = table.stateManager.select.ranges; + const justOneCellSelected = + selectedRanges.length === 1 && + selectedRanges[0].start.col === selectedRanges[0].end.col && + selectedRanges[0].start.row === selectedRanges[0].end.row; if (justOneCellSelected) { if ( (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) && From 7fc134a00ce1d86b780f227e672b88cb3d5f6fce Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 24 Dec 2025 17:53:38 +0800 Subject: [PATCH 6/7] fix: editor support keydown chinese #4847 --- packages/vtable/src/edit/edit-manager.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index 52c4ead75b..868579f597 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -335,9 +335,6 @@ export class EditManager { this.listenersId.forEach(id => { this.table.off(id); }); - if (this.editInputElement) { - this.editInputElement.release(); - } } } From 1dbdc0982cff7a40e6974b8591e9e90a379db10e Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 24 Dec 2025 17:55:40 +0800 Subject: [PATCH 7/7] fix: editor support keydown chinese #4847 --- packages/vtable/examples/editor/date-editor.ts | 4 +++- packages/vtable/src/edit/edit-manager.ts | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/vtable/examples/editor/date-editor.ts b/packages/vtable/examples/editor/date-editor.ts index 7f1456cbd4..7ec207b300 100644 --- a/packages/vtable/examples/editor/date-editor.ts +++ b/packages/vtable/examples/editor/date-editor.ts @@ -179,8 +179,10 @@ export function createTable() { heightMode: 'autoHeight', autoWrapText: true, editCellTrigger: ['keydown', 'doubleclick'], + keyboardOptions: { - moveFocusCellOnEnter: true + moveFocusCellOnEnter: true, + moveEditCellOnArrowKeys: true }, editor(args) { return new InputEditor({}); diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index 868579f597..458f39c679 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -317,9 +317,6 @@ export class EditManager { this.editingEditor = null; this.isValidatingValue = false; this.beginTriggerEditCellMode = null; - if (this.editInputElement) { - this.editInputElement.hide(); - } } cancelEdit() {