Skip to content

Commit a1e4a27

Browse files
authored
Merge pull request #4883 from VisActor/fix/edit_cell_trigger_error_prepareEditLogic
Fix/edit cell trigger error prepare edit logic
2 parents c1a155d + 2f9756b commit a1e4a27

8 files changed

Lines changed: 103 additions & 22 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "fix: prepareEdit logic occor some bug\n\n",
5+
"type": "none",
6+
"packageName": "@visactor/vtable"
7+
}
8+
],
9+
"packageName": "@visactor/vtable",
10+
"email": "892739385@qq.com"
11+
}

packages/vtable-editors/src/input-editor.ts

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export class InputEditor implements IEditor {
1414
table?: any;
1515
col?: number;
1616
row?: number;
17+
// 存储事件处理器,用于在移除元素前解绑
18+
private eventHandlers: Array<{ type: string; handler: EventListener }> = [];
1719
constructor(editorConfig?: InputEditorConfig) {
1820
this.editorConfig = editorConfig;
1921
}
@@ -22,6 +24,9 @@ export class InputEditor implements IEditor {
2224
}
2325

2426
createElement() {
27+
// 清空之前的事件处理器(如果存在)
28+
this.eventHandlers = [];
29+
2530
const input = document.createElement('input');
2631
input.setAttribute('type', 'text');
2732

@@ -37,37 +42,50 @@ export class InputEditor implements IEditor {
3742
input.style.borderRadius = '0px';
3843
input.style.border = '2px solid #d9d9d9';
3944
// #region 为了保证input在focus时,没有圆角
40-
input.addEventListener('focus', () => {
45+
const focusHandler = () => {
4146
input.style.borderColor = '#4A90E2';
4247
input.style.outline = 'none';
43-
});
48+
};
49+
input.addEventListener('focus', focusHandler);
50+
this.eventHandlers.push({ type: 'focus', handler: focusHandler });
4451

45-
input.addEventListener('blur', e => {
52+
const blurHandler: EventListener = (e: Event) => {
4653
input.style.borderColor = '#d9d9d9';
4754
// input.style.boxShadow = 'none';
48-
if (this.table && this.element.style.opacity === '0') {
55+
if (this.table && this.element && this.element.style.opacity === '0') {
4956
const selectCell = this.table.stateManager.select.cellPos;
5057
if (selectCell.col !== this.col || selectCell.row !== this.row) {
5158
this.onEnd();
5259
}
5360
}
54-
});
61+
};
62+
input.addEventListener('blur', blurHandler);
63+
this.eventHandlers.push({ type: 'blur', handler: blurHandler });
5564
// #endregion
5665
this.element = input;
5766
this.container.appendChild(input);
5867

5968
// 监听键盘事件
60-
input.addEventListener('keydown', (e: KeyboardEvent) => {
61-
if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
69+
const keydownHandler: EventListener = (e: Event) => {
70+
const keyboardEvent = e as KeyboardEvent;
71+
if (
72+
keyboardEvent.key === 'a' &&
73+
(keyboardEvent.ctrlKey || keyboardEvent.metaKey) &&
74+
this.table.editorManager?.editingEditor
75+
) {
6276
// 阻止冒泡 防止处理成表格全选事件
63-
e.stopPropagation();
77+
keyboardEvent.stopPropagation();
6478
}
65-
});
79+
};
80+
input.addEventListener('keydown', keydownHandler);
81+
this.eventHandlers.push({ type: 'keydown', handler: keydownHandler });
6682

6783
// hack for preventing drag touch cause page jump
68-
input.addEventListener('wheel', e => {
84+
const wheelHandler: EventListener = (e: Event) => {
6985
e.preventDefault();
70-
});
86+
};
87+
input.addEventListener('wheel', wheelHandler);
88+
this.eventHandlers.push({ type: 'wheel', handler: wheelHandler });
7189
}
7290

7391
setValue(value: string) {
@@ -92,6 +110,11 @@ export class InputEditor implements IEditor {
92110
}
93111
if (!this.element) {
94112
this.createElement();
113+
} else {
114+
if (!container.contains(this.element)) {
115+
this.element.parentElement.removeChild(this.element);
116+
this.container.appendChild(this.element);
117+
}
95118
}
96119
this.element.style.opacity = '0';
97120
//这个pointerEvents = 'none'很重要,如果没有的话会引起vtable.getElement()元素和这里的element元素的focus和blur的切换,
@@ -113,6 +136,11 @@ export class InputEditor implements IEditor {
113136
if (referencePosition?.rect) {
114137
this.adjustPosition(referencePosition.rect);
115138
}
139+
} else {
140+
if (!container.contains(this.element)) {
141+
this.element.parentElement.removeChild(this.element);
142+
this.container.appendChild(this.element);
143+
}
116144
}
117145
if (value !== undefined && value !== null) {
118146
this.setValue(value);
@@ -144,10 +172,35 @@ export class InputEditor implements IEditor {
144172

145173
onEnd() {
146174
// do nothing
147-
if (this.container?.contains(this.element)) {
148-
this.container.removeChild(this.element);
149-
this.element = undefined;
175+
if (!this.element) {
176+
return;
177+
}
178+
179+
// 保存元素引用,避免在移除过程中被其他代码修改
180+
const element = this.element;
181+
182+
// 先移除所有事件监听器,避免在 removeChild 时触发 blur 等事件
183+
this.eventHandlers.forEach(({ type, handler }) => {
184+
element.removeEventListener(type, handler);
185+
});
186+
this.eventHandlers = [];
187+
188+
// 检查元素的父节点是否存在,确保元素还在 DOM 中
189+
const parentNode = element.parentNode;
190+
if (parentNode) {
191+
try {
192+
// 事件监听器已经移除,可以安全地移除元素,不会触发 blur 等事件
193+
parentNode.removeChild(element);
194+
} catch (error) {
195+
// 如果元素已经被移除或移动,忽略 NotFoundError
196+
if (error instanceof Error && error.name !== 'NotFoundError') {
197+
throw error;
198+
}
199+
}
150200
}
201+
202+
// 清空引用
203+
this.element = undefined;
151204
}
152205

153206
isEditorElement(target: HTMLElement) {

packages/vtable-plugins/src/excel-edit-cell-keyboard.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ListTable, BaseTableAPI, TYPES, pluginsDefinition } from '@visacto
22
import { TABLE_EVENT_TYPE } from '@visactor/vtable';
33
import type { TableEvents } from '@visactor/vtable/src/core/TABLE_EVENT_TYPE';
44
import type { EventArg } from './types';
5+
import type { IEditor } from '@visactor/vtable-editors';
56
export enum ExcelEditCellKeyboardResponse {
67
ENTER = 'enter',
78
TAB = 'tab',
@@ -123,7 +124,14 @@ export class ExcelEditCellKeyboardPlugin implements pluginsDefinition.IVTablePlu
123124
) {
124125
//响应删除键,删除
125126
const selectCells = this.table.getSelectedCellInfos();
126-
if (selectCells?.length > 0 && document.activeElement === this.table.getElement()) {
127+
if (
128+
selectCells?.length > 0 &&
129+
(document.activeElement === this.table.getElement() ||
130+
Object.values(this.table.editorManager.cacheLastSelectedCellEditor || {}).some(
131+
// 处理情况:没有开始编辑但编辑器及编辑输入框已经存在的情况下(editCellTrigger为keydown)判断当前激活的是cacheLastSelectedCellEditor中的input也应该响应删除单元格
132+
(editor: IEditor) => editor.getInputElement?.() === document.activeElement
133+
))
134+
) {
127135
// 如果选中的是范围,则删除范围内的所有单元格
128136
deleteSelectRange(selectCells, this.table, this.pluginOptions?.deleteWorkOnEditableCell ?? true);
129137
// 阻止事件传播和默认行为

packages/vtable-sheet/examples/sheet/sheet.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export function createTable() {
99
// defaultColWidth: 100,
1010
sheets: [
1111
{
12+
keyboardOptions: { selectAllOnCtrlA: true },
1213
rowCount: 200,
1314
columnCount: 10,
1415
sheetKey: 'sheet1',

packages/vtable-sheet/src/formula/formula-editor.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,10 @@ export class FormulaInputEditor extends VTable_editors.InputEditor {
240240
}
241241
//解绑所有事件
242242
// 解绑事件(在需要解绑的地方)
243-
this.eventHandlers.forEach(({ type, handler }) => {
244-
this.element.removeEventListener(type, handler);
245-
});
243+
this.element &&
244+
this.eventHandlers.forEach(({ type, handler }) => {
245+
this.element.removeEventListener(type, handler);
246+
});
246247
super.onEnd();
247248
}
248249

packages/vtable/src/ListTable.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,8 @@ export class ListTable extends BaseTable implements ListTableAPI {
15361536
if (lastSelectedCellEditor) {
15371537
return lastSelectedCellEditor;
15381538
}
1539+
Object.values(this.editorManager.cacheLastSelectedCellEditor).forEach((editor: IEditor) => editor?.onEnd?.());
1540+
this.editorManager.cacheLastSelectedCellEditor = {};
15391541
const define = this.getBodyColumnDefine(col, row);
15401542
let editor = this.isHeader(col, row)
15411543
? (define as ColumnDefine)?.headerEditor ?? this.options.headerEditor
@@ -1554,8 +1556,9 @@ export class ListTable extends BaseTable implements ListTableAPI {
15541556
if (typeof editor === 'string') {
15551557
editor = editors.get(editor);
15561558
}
1557-
this.editorManager.cacheLastSelectedCellEditor = {};
1558-
this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`] = editor as IEditor;
1559+
if (editor) {
1560+
this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`] = editor as IEditor;
1561+
}
15591562
return editor as IEditor;
15601563
}
15611564
/** 检查单元格是否定义过编辑器 不管编辑器是否有效 只要有定义就返回true */

packages/vtable/src/PivotTable.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,8 @@ export class PivotTable extends BaseTable implements PivotTableAPI {
17591759
if (lastSelectedCellEditor) {
17601760
return lastSelectedCellEditor;
17611761
}
1762+
Object.values(this.editorManager.cacheLastSelectedCellEditor).forEach((editor: IEditor) => editor?.onEnd?.());
1763+
this.editorManager.cacheLastSelectedCellEditor = {};
17621764
let editor;
17631765
if (this.isCornerHeader(col, row)) {
17641766
const define = this.getHeaderDefine(col, row);
@@ -1783,8 +1785,9 @@ export class PivotTable extends BaseTable implements PivotTableAPI {
17831785
if (typeof editor === 'string') {
17841786
editor = editors.get(editor);
17851787
}
1786-
this.editorManager.cacheLastSelectedCellEditor = {};
1787-
this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`] = editor as IEditor;
1788+
if (editor) {
1789+
this.editorManager.cacheLastSelectedCellEditor[`${col}-${row}`] = editor as IEditor;
1790+
}
17881791
return editor as IEditor;
17891792
}
17901793
/** 检查单元格是否定义过编辑器 不管编辑器是否有效 只要有定义就返回true */

packages/vtable/src/edit/edit-manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export class EditManager {
1515
editCell: { col: number; row: number };
1616
listenersId: number[] = [];
1717
beginTriggerEditCellMode: 'doubleclick' | 'click' | 'keydown';
18+
/** 主要为了editor配置成函数的情况下,点击单元格流程造成接连调用getEditor()可能生成多个editor实例,所以需要缓存 */
1819
cacheLastSelectedCellEditor: Record<string, IEditor> = {};
1920
constructor(table: BaseTableAPI) {
2021
this.table = table;

0 commit comments

Comments
 (0)