From 22b114b17e5e53764da496b34d1c3b7043112014 Mon Sep 17 00:00:00 2001 From: nikzart Date: Tue, 7 Jul 2026 02:02:52 +0530 Subject: [PATCH 1/2] fix: Sender skill closable.onClose not triggered when deleting skill via Backspace Fire closable.onClose in the keyboard deletion branch of handleDeleteOperation, matching the close-button click behavior, so external code can observe skill removal via Backspace. close #1955, close #1938 --- .../components/sender/__tests__/slot.test.tsx | 40 +++++++++++++++++++ .../sender/components/SlotTextArea.tsx | 4 ++ 2 files changed, 44 insertions(+) diff --git a/packages/x/components/sender/__tests__/slot.test.tsx b/packages/x/components/sender/__tests__/slot.test.tsx index e1cff6a5c..3e39c388e 100644 --- a/packages/x/components/sender/__tests__/slot.test.tsx +++ b/packages/x/components/sender/__tests__/slot.test.tsx @@ -523,6 +523,46 @@ describe('Sender Slot Component', () => { setupDOMMocks(customSelectionMock, customRangeMock); fireEvent.keyDown(dom, { key: 'Backspace' }); }); + it('should trigger closable.onClose when removing skill via Backspace', () => { + const onClose = jest.fn(); + const ref = createRef(); + const { container } = render( + , + ); + const dom = ref.current?.inputElement as HTMLElement; + const skillDom = container.querySelector('.ant-sender-skill') as HTMLElement; + expect(ref.current).toBeDefined(); + expect(skillDom).toBeDefined(); + + const customSelectionMock = { + rangeCount: 1, + focusOffset: 0, + anchorNode: dom, + removeAllRanges: jest.fn(), + addRange: jest.fn(), + }; + + Object.defineProperty(dom, 'previousSibling', { + value: skillDom, + configurable: true, + }); + + const customRangeMock = createMockRange(); + setupDOMMocks(customSelectionMock, customRangeMock); + fireEvent.keyDown(dom, { key: 'Backspace' }); + + expect(onClose).toHaveBeenCalledTimes(1); + expect(container.querySelector('.ant-sender-skill')).not.toBeInTheDocument(); + }); it('should handle skill removal and addition', () => { const { rerender, container } = render( ((_, ref) => { if (skillKey) { e.preventDefault(); removeSkill(); + // 与点击关闭行为保持一致,删除后补发 closable.onClose + if (skill?.closable && typeof skill.closable === 'object') { + skill.closable.onClose?.(e as unknown as React.MouseEvent); + } return true; } } From 523a1999e68d83ea5b4ca73bb0a212a6efc2de7e Mon Sep 17 00:00:00 2001 From: nikzart Date: Tue, 7 Jul 2026 02:20:11 +0530 Subject: [PATCH 2/2] fix: type skill closable.onClose as React.SyntheticEvent for keyboard removal Address review feedback: - Widen closable.onClose (interface, ClosableConfig, docs) from React.MouseEventHandler to (event: React.SyntheticEvent) => void, since the callback now also receives the keyboard event on Backspace removal. Removes the KeyboardEvent-to-MouseEvent cast. - Strengthen test assertions (not.toBeNull / toBeInTheDocument). --- packages/x/components/sender/__tests__/slot.test.tsx | 4 ++-- packages/x/components/sender/components/Skill.tsx | 2 +- packages/x/components/sender/components/SlotTextArea.tsx | 2 +- packages/x/components/sender/index.en-US.md | 3 ++- packages/x/components/sender/index.zh-CN.md | 3 ++- packages/x/components/sender/interface.ts | 3 ++- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/x/components/sender/__tests__/slot.test.tsx b/packages/x/components/sender/__tests__/slot.test.tsx index 3e39c388e..452cf686d 100644 --- a/packages/x/components/sender/__tests__/slot.test.tsx +++ b/packages/x/components/sender/__tests__/slot.test.tsx @@ -540,8 +540,8 @@ describe('Sender Slot Component', () => { ); const dom = ref.current?.inputElement as HTMLElement; const skillDom = container.querySelector('.ant-sender-skill') as HTMLElement; - expect(ref.current).toBeDefined(); - expect(skillDom).toBeDefined(); + expect(ref.current).not.toBeNull(); + expect(skillDom).toBeInTheDocument(); const customSelectionMock = { rangeCount: 1, diff --git a/packages/x/components/sender/components/Skill.tsx b/packages/x/components/sender/components/Skill.tsx index 50d66dd2c..1e7326f87 100644 --- a/packages/x/components/sender/components/Skill.tsx +++ b/packages/x/components/sender/components/Skill.tsx @@ -6,7 +6,7 @@ import type { SkillType } from '../interface'; interface ClosableConfig { closeIcon?: React.ReactNode; - onClose?: React.MouseEventHandler; + onClose?: (event: React.SyntheticEvent) => void; disabled?: boolean; } diff --git a/packages/x/components/sender/components/SlotTextArea.tsx b/packages/x/components/sender/components/SlotTextArea.tsx index b33684b47..f183af0bb 100644 --- a/packages/x/components/sender/components/SlotTextArea.tsx +++ b/packages/x/components/sender/components/SlotTextArea.tsx @@ -527,7 +527,7 @@ const SlotTextArea = React.forwardRef((_, ref) => { removeSkill(); // 与点击关闭行为保持一致,删除后补发 closable.onClose if (skill?.closable && typeof skill.closable === 'object') { - skill.closable.onClose?.(e as unknown as React.MouseEvent); + skill.closable.onClose?.(e); } return true; } diff --git a/packages/x/components/sender/index.en-US.md b/packages/x/components/sender/index.en-US.md index 88c657671..e97dc3f1e 100644 --- a/packages/x/components/sender/index.en-US.md +++ b/packages/x/components/sender/index.en-US.md @@ -78,7 +78,8 @@ interface SkillType { | boolean | { closeIcon?: React.ReactNode; - onClose?: React.MouseEventHandler; + // Triggered when the skill is removed by clicking the close button or pressing Backspace + onClose?: (event: React.SyntheticEvent) => void; disabled?: boolean; }; } diff --git a/packages/x/components/sender/index.zh-CN.md b/packages/x/components/sender/index.zh-CN.md index 3bbee1423..39ca367a1 100644 --- a/packages/x/components/sender/index.zh-CN.md +++ b/packages/x/components/sender/index.zh-CN.md @@ -80,7 +80,8 @@ interface SkillType { | boolean | { closeIcon?: React.ReactNode; - onClose?: React.MouseEventHandler; + // 点击关闭按钮或使用 Backspace 删除时触发 + onClose?: (event: React.SyntheticEvent) => void; disabled?: boolean; }; } diff --git a/packages/x/components/sender/interface.ts b/packages/x/components/sender/interface.ts index 871d8aad0..808e8fdbd 100644 --- a/packages/x/components/sender/interface.ts +++ b/packages/x/components/sender/interface.ts @@ -59,7 +59,8 @@ export interface SkillType { | boolean | { closeIcon?: React.ReactNode; - onClose?: React.MouseEventHandler; + /** 点击关闭按钮或使用 Backspace 删除时触发 */ + onClose?: (event: React.SyntheticEvent) => void; disabled?: boolean; }; }