From 2a7b8a36a19e5ae0f67b48667ca0080a3fdd93ed Mon Sep 17 00:00:00 2001 From: yaolongfei <2991205548@qq.com> Date: Sun, 31 May 2026 16:21:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=20divider=20?= =?UTF-8?q?=E6=92=A4=E9=94=80=E5=90=8E=E8=BE=93=E5=85=A5=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit beforeinput targetRange 映射改为容错模式 补充回归测试覆盖映射失败与 stale DOM 场景 增加 core patch changeset Closes: #892 --- .changeset/tough-snails-dance.md | 5 ++ .../event-handlers/before-input.test.ts | 56 +++++++++++++++++++ .../text-area/event-handlers/beforeInput.ts | 18 ++++-- 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 .changeset/tough-snails-dance.md diff --git a/.changeset/tough-snails-dance.md b/.changeset/tough-snails-dance.md new file mode 100644 index 000000000..a32eca5e3 --- /dev/null +++ b/.changeset/tough-snails-dance.md @@ -0,0 +1,5 @@ +--- +'@wangeditor-next/core': patch +--- + +fix beforeinput target range mapping after divider undo to tolerate stale DOM nodes and avoid "Cannot resolve a Slate node from DOM node" errors when typing diff --git a/packages/core/__tests__/text-area/event-handlers/before-input.test.ts b/packages/core/__tests__/text-area/event-handlers/before-input.test.ts index 9c897358e..2b7c3fd3e 100644 --- a/packages/core/__tests__/text-area/event-handlers/before-input.test.ts +++ b/packages/core/__tests__/text-area/event-handlers/before-input.test.ts @@ -289,6 +289,62 @@ describe('handleBeforeInput', () => { expect(Editor.insertText).toHaveBeenCalledWith(editor, 'same') }) + it('skips reselection when target range cannot be mapped to Slate', () => { + const editor = { + selection: createSelection(false), + getConfig: () => ({ readOnly: false }), + insertData: vi.fn(), + } as any + const targetRange = { startContainer: document.createTextNode('x') } + const event = { + inputType: 'insertText', + data: 'hello', + dataTransfer: null, + target: {}, + preventDefault: vi.fn(), + getTargetRanges: () => [targetRange], + } as any + + vi.spyOn(helpers, 'hasEditableTarget').mockReturnValue(true) + vi.spyOn(DomEditor, 'toSlateRange').mockReturnValue(null as any) + vi.spyOn(Transforms, 'select').mockImplementation(() => {}) + vi.spyOn(Editor, 'insertText').mockImplementation(() => {}) + + expect(() => handleBeforeInput(event, {} as any, editor)).not.toThrow() + expect(DomEditor.toSlateRange).toHaveBeenCalledWith(editor, targetRange, { + exactMatch: false, + suppressThrow: true, + }) + expect(Transforms.select).not.toHaveBeenCalled() + expect(Editor.insertText).toHaveBeenCalledWith(editor, 'hello') + }) + + it('does not throw when target range points to stale DOM nodes', () => { + const editor = { + selection: createSelection(false), + getConfig: () => ({ readOnly: false }), + insertData: vi.fn(), + } as any + const targetRange = { startContainer: document.createTextNode('x') } + const event = { + inputType: 'insertText', + data: 'hello', + dataTransfer: null, + target: {}, + preventDefault: vi.fn(), + getTargetRanges: () => [targetRange], + } as any + + vi.spyOn(helpers, 'hasEditableTarget').mockReturnValue(true) + vi.spyOn(DomEditor, 'toSlateRange').mockImplementation(() => { + throw new Error('Cannot resolve a Slate node from DOM node') + }) + vi.spyOn(Editor, 'insertText').mockImplementation(() => {}) + + expect(() => handleBeforeInput(event, {} as any, editor)).not.toThrow() + expect(Editor.insertText).toHaveBeenCalledWith(editor, 'hello') + }) + it('does not delete when selection partially covers a table', () => { const editor = { selection: createSelection(true), diff --git a/packages/core/src/text-area/event-handlers/beforeInput.ts b/packages/core/src/text-area/event-handlers/beforeInput.ts index aea4f6565..a6bb9eed0 100644 --- a/packages/core/src/text-area/event-handlers/beforeInput.ts +++ b/packages/core/src/text-area/event-handlers/beforeInput.ts @@ -56,12 +56,20 @@ function handleBeforeInput(e: Event, textarea: TextArea, editor: IDomEditor) { const [targetRange] = event.getTargetRanges() if (targetRange) { - const range = DomEditor.toSlateRange(editor, targetRange, { - exactMatch: false, - suppressThrow: false, - }) + let range: Range | null = null + + try { + range = DomEditor.toSlateRange(editor, targetRange, { + exactMatch: false, + suppressThrow: true, + }) + } catch { + // Undo/redo or IME transitions can leave beforeinput target ranges + // pointing to stale DOM nodes for a short time. + range = null + } - if (!selection || !Range.equals(selection, range)) { + if (range && (!selection || !Range.equals(selection, range))) { Transforms.select(editor, range) } }