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 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 diff --git a/packages/vtable-editors/src/input-editor.ts b/packages/vtable-editors/src/input-editor.ts index 735570752e..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,10 +14,12 @@ export class InputEditor implements IEditor { table?: any; col?: number; row?: number; - constructor(editorConfig?: InputEditorConfig) { this.editorConfig = editorConfig; } + getInputElement(): HTMLInputElement { + return this.element; + } createElement() { const input = document.createElement('input'); @@ -40,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; @@ -69,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; @@ -78,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 } @@ -112,8 +146,8 @@ export class InputEditor implements IEditor { // do nothing if (this.container?.contains(this.element)) { this.container.removeChild(this.element); + this.element = undefined; } - this.element = undefined; } isEditorElement(target: HTMLElement) { diff --git a/packages/vtable-editors/src/types.ts b/packages/vtable-editors/src/types.ts index 315cedc967..3937f9b7b0 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. * @@ -46,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. @@ -68,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 @@ -96,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/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/examples/editor/input-editor.ts b/packages/vtable/examples/editor/input-editor.ts index eba310802e..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({}); @@ -136,6 +137,7 @@ export function createTable() { const option: VTable.ListTableConstructorOptions = { emptyTip: true, container: document.getElementById(CONTAINER_ID), + editCellTrigger: 'keydown', columns: [ { field: 'progress', @@ -170,6 +172,12 @@ export function createTable() { } } ], + hover: { + highlightMode: 'cross' + }, + keyboardOptions: { + selectAllOnCtrlA: true + }, showFrozenIcon: true, //显示VTable内置冻结列图标 widthMode: 'standard', autoFillWidth: true, @@ -185,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/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/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/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..458f39c679 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -1,7 +1,7 @@ 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'; @@ -15,17 +15,17 @@ export class EditManager { editCell: { col: number; row: number }; listenersId: number[] = []; beginTriggerEditCellMode: 'doubleclick' | 'click' | 'keydown'; + cacheLastSelectedCellEditor: Record = {}; constructor(table: BaseTableAPI) { this.table = table; - this.bindEvent(); + const { editCellTrigger = 'doubleclick' } = table.options as ListTableConstructorOptions; + 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,10 +62,74 @@ 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)); + // 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 }); } }); - - this.listenersId.push(doubleClickEventId, clickEventId); + 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)); + 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, selectedChangedEventId); // handler.on(this.table.getElement(), 'wheel', (e: WheelEvent) => { // this.completeEdit(); @@ -77,12 +141,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"); @@ -173,6 +238,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/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index d9563cf4d8..f96306be58 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -22,7 +22,26 @@ 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) { + // 检查是否是编辑器内部的 input + 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(); }); @@ -117,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); } @@ -206,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); } @@ -218,19 +238,27 @@ 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, ''); + 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'))) && + !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