From 7c4ad0d49c5f3d676e1a7009f9e4a6f0899e9c87 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 22 Apr 2026 18:55:55 +0800 Subject: [PATCH 1/3] fix: edit cell invalidateNotExit #5114 --- packages/vtable/examples/debug/issue-5114.ts | 49 ++++++++++++ packages/vtable/examples/menu.ts | 4 + .../src/event/listener/container-dom.ts | 80 ++++++++++++------- 3 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 packages/vtable/examples/debug/issue-5114.ts diff --git a/packages/vtable/examples/debug/issue-5114.ts b/packages/vtable/examples/debug/issue-5114.ts new file mode 100644 index 000000000..86495c19f --- /dev/null +++ b/packages/vtable/examples/debug/issue-5114.ts @@ -0,0 +1,49 @@ +import * as VTable from '../../src'; +import { InputEditor, ValidateEnum } from '@visactor/vtable-editors'; +const ListTable = VTable.ListTable; +const CONTAINER_ID = 'vTable'; +const input_editor = new InputEditor({}); +VTable.register.editor('input', input_editor); + +export function createTable() { + const personsDataSource = [ + { progress: 100, id: 1, name: 'a' }, + { progress: 80, id: 2, name: 'b' }, + { progress: 1, id: 3, name: 'c' }, + { progress: 55, id: 4, name: 'd' }, + { progress: 28, id: 5, name: 'e' } + ]; + + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + editCellTrigger: 'click', + columns: [ + { + field: 'progress', + title: 'progress', + width: 200, + editor: 'input' + }, + { + field: 'id', + title: 'ID', + width: 200 + } + ], + widthMode: 'standard', + records: personsDataSource + }; + + const instance = new ListTable(option); + + // 配置 validateValue,当值为空时返回 invalidate-not-exit + input_editor.validateValue = (newValue, _oldValue, _cell, _table) => { + if (newValue === '' || newValue === null || newValue === undefined) { + return ValidateEnum.invalidateNotExit; + } + return ValidateEnum.validateExit; + }; + + const w = window as unknown as { tableInstance?: unknown }; + w.tableInstance = instance; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 63cab876d..86653dce7 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -29,6 +29,10 @@ export const menus = [ { path: 'debug', name: 'mem' + }, + { + path: 'debug', + name: 'issue-5114' } ] }, diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index be213623e..23e2740aa 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -4,7 +4,7 @@ import type { ListTableConstructorOptions, MousePointerMultiCellEvent } from '.. import { InteractionState, type KeydownEvent, type ListTableAPI } from '../../ts-types'; import { TABLE_EVENT_TYPE } from '../../core/TABLE_EVENT_TYPE'; import { handleWhell } from '../scroll'; -import { browser, getPromiseValue } from '../../tools/helper'; +import { getPromiseValue } from '../../tools/helper'; import type { EventManager } from '../event'; import { getPixelRatio } from '../../tools/pixel-ratio'; import { endResizeCol, endResizeRow } from './table-group'; @@ -15,6 +15,18 @@ export function bindContainerDomListener(eventManager: EventManager) { const table = eventManager.table; const stateManager = table.stateManager; const handler: EventHandler = table.internalProps.handler; + const focusEditingInput = () => { + (table as ListTableAPI).editorManager?.editingEditor?.getInputElement?.()?.focus?.(); + }; + const afterCompleteEdit = (completeEditResult: boolean | Promise | undefined, onSuccess: () => void) => { + getPromiseValue(completeEditResult, (isCompleteEdit: boolean) => { + if (isCompleteEdit === false) { + focusEditingInput(); + return; + } + onSuccess(); + }); + }; // handler.on(table.getElement(), 'mousedown', (e: MouseEvent) => { // if (table.eventManager.isPointerDownOnTable) { @@ -81,8 +93,8 @@ export function bindContainerDomListener(eventManager: EventManager) { // 如果不加这句话 外部监听了键盘事件 会影响表格本身的移动格子功能,例如自定义日历编辑器的日期选择pickday.js //可能会引起其他问题 例如自定义实现了日历编辑器 里面切换日期左右键可能失效,这个时候建议监听VTable实例的事件keydown e.stopPropagation(); - let targetCol; - let targetRow; + let targetCol: number; + let targetRow: number; // 处理向上箭头键 if (e.key === 'ArrowUp') { @@ -139,16 +151,18 @@ export function bindContainerDomListener(eventManager: EventManager) { } 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) && isEditingCell) { - // 开启了方向键切换编辑单元格 并且当前已经在编辑状态下 切换到下一个需先退出再进入下个单元格的编辑 - if ((table as ListTableAPI).getEditor(targetCol, targetRow)) { - (table as ListTableAPI).editorManager?.startEditCell(targetCol, targetRow); + const completeEditResult = (table as ListTableAPI).editorManager?.completeEdit(); + afterCompleteEdit(completeEditResult, () => { + table.getElement().focus(); + const enableShiftSelectMode = table.options.keyboardOptions?.shiftMultiSelect ?? true; + table.selectCell(targetCol, targetRow, e.shiftKey && enableShiftSelectMode); + if ((table.options.keyboardOptions?.moveEditCellOnArrowKeys ?? false) && isEditingCell) { + // 开启了方向键切换编辑单元格 并且当前已经在编辑状态下 切换到下一个需先退出再进入下个单元格的编辑 + if ((table as ListTableAPI).getEditor(targetCol, targetRow)) { + (table as ListTableAPI).editorManager?.startEditCell(targetCol, targetRow); + } } - } + }); } else if (e.key === 'Escape') { (table as ListTableAPI).editorManager?.cancelEdit(); table.getElement().focus(); @@ -157,20 +171,22 @@ export function bindContainerDomListener(eventManager: EventManager) { if ((table as ListTableAPI).editorManager?.editingEditor) { // 如果是结束当前编辑,且有主动监听keydown事件,则先触发keydown事件,之后再结束编辑 handleKeydownListener(e); - (table as ListTableAPI).editorManager?.completeEdit(); - table.getElement().focus(); + const completeEditResult = (table as ListTableAPI).editorManager?.completeEdit(); + afterCompleteEdit(completeEditResult, () => { + table.getElement().focus(); - if (table.options.keyboardOptions?.moveFocusCellOnEnter === true) { - // 利用enter键选中下一个单元格 - const targetCol = stateManager.select.cellPos.col; - const targetRow = Math.min(table.rowCount - 1, Math.max(0, stateManager.select.cellPos.row + 1)); - // 如果是不支持选中的单元格 则退出 - if (isCellDisableSelect(table, targetCol, targetRow)) { - return; + if (table.options.keyboardOptions?.moveFocusCellOnEnter === true) { + // 利用enter键选中下一个单元格 + const targetCol = stateManager.select.cellPos.col; + const targetRow = Math.min(table.rowCount - 1, Math.max(0, stateManager.select.cellPos.row + 1)); + // 如果是不支持选中的单元格 则退出 + if (isCellDisableSelect(table, targetCol, targetRow)) { + return; + } + const enableShiftSelectMode = table.options.keyboardOptions?.shiftMultiSelect ?? true; + table.selectCell(targetCol, targetRow, e.shiftKey && enableShiftSelectMode); } - const enableShiftSelectMode = table.options.keyboardOptions?.shiftMultiSelect ?? true; - table.selectCell(targetCol, targetRow, e.shiftKey && enableShiftSelectMode); - } + }); // 直接返回,不再触发最后的keydown监听事件相关代码 return; } @@ -227,14 +243,16 @@ export function bindContainerDomListener(eventManager: EventManager) { } 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 (isEditingCell) { - if ((table as ListTableAPI).getEditor(targetCol, targetRow)) { - (table as ListTableAPI).editorManager?.startEditCell(targetCol, targetRow); + const completeEditResult = (table as ListTableAPI).editorManager?.completeEdit(); + afterCompleteEdit(completeEditResult, () => { + table.getElement().focus(); + table.selectCell(targetCol, targetRow); + if (isEditingCell) { + if ((table as ListTableAPI).getEditor(targetCol, targetRow)) { + (table as ListTableAPI).editorManager?.startEditCell(targetCol, targetRow); + } } - } + }); } } } else if (!(e.ctrlKey || e.metaKey)) { From b1735ed054618a2810539b4acd8299a5f1709c4f Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 22 Apr 2026 18:57:28 +0800 Subject: [PATCH 2/3] docs: update changlog of rush --- ...ix-editCellInvalidateNotExit_2026-04-22-10-57.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/fix-editCellInvalidateNotExit_2026-04-22-10-57.json diff --git a/common/changes/@visactor/vtable/fix-editCellInvalidateNotExit_2026-04-22-10-57.json b/common/changes/@visactor/vtable/fix-editCellInvalidateNotExit_2026-04-22-10-57.json new file mode 100644 index 000000000..72bac887c --- /dev/null +++ b/common/changes/@visactor/vtable/fix-editCellInvalidateNotExit_2026-04-22-10-57.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: edit cell invalidateNotExit #5114\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 5994c3dd80675155e1a789d93827fe9ef9472b9f Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 22 Apr 2026 19:40:28 +0800 Subject: [PATCH 3/3] fix: edit cell invalidateNotExit #5114 --- packages/vtable/src/event/listener/container-dom.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index 23e2740aa..5ea6f1943 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -228,8 +228,8 @@ export function bindContainerDomListener(eventManager: EventManager) { e.preventDefault(); - let targetCol; - let targetRow; + let targetCol: number; + let targetRow: number; if (stateManager.select.cellPos.col === table.colCount - 1) { targetRow = Math.min(table.rowCount - 1, stateManager.select.cellPos.row + 1); targetCol = table.rowHeaderLevelCount;