From 9e17b9c0fb281c875ea1ee785830d1ed587695f7 Mon Sep 17 00:00:00 2001 From: Bogdan Malizhev Date: Fri, 7 Aug 2026 12:13:48 +0200 Subject: [PATCH] fix: insert mentions on first click Prevent mention row mousedown events from reaching the document-level selection handler, which remounted the popover before click could fire. Closes #3878 --- .../v1/components/popovers/Mentions.test.ts | 38 +++++++++++++++++++ .../v1/components/popovers/Mentions.vue | 7 ++-- .../popover-plugin/popover-plugin.js | 2 +- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 packages/super-editor/src/editors/v1/components/popovers/Mentions.test.ts diff --git a/packages/super-editor/src/editors/v1/components/popovers/Mentions.test.ts b/packages/super-editor/src/editors/v1/components/popovers/Mentions.test.ts new file mode 100644 index 0000000000..a44d6e9b21 --- /dev/null +++ b/packages/super-editor/src/editors/v1/components/popovers/Mentions.test.ts @@ -0,0 +1,38 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import Mentions from './Mentions.vue'; + +describe('Mentions.vue rendering', () => { + let wrapper: ReturnType | undefined; + let documentMousedown: ReturnType | undefined; + + afterEach(() => { + wrapper?.unmount(); + if (documentMousedown) document.removeEventListener('mousedown', documentMousedown); + }); + + it('keeps the user row mounted through the first click', () => { + const user = { name: 'Alice', email: 'alice@example.com', role: 'editor' }; + const insertMention = vi.fn(); + documentMousedown = vi.fn(); + document.addEventListener('mousedown', documentMousedown); + + wrapper = mount(Mentions, { + props: { + users: [user], + insertMention, + }, + }); + + const row = wrapper.get('.user-row'); + const mousedown = new MouseEvent('mousedown', { bubbles: true, cancelable: true }); + row.element.dispatchEvent(mousedown); + + expect(mousedown.defaultPrevented).toBe(true); + expect(documentMousedown).not.toHaveBeenCalled(); + + row.element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); + expect(insertMention).toHaveBeenCalledOnce(); + expect(insertMention).toHaveBeenCalledWith(user); + }); +}); diff --git a/packages/super-editor/src/editors/v1/components/popovers/Mentions.vue b/packages/super-editor/src/editors/v1/components/popovers/Mentions.vue index 0eb3021500..999aed5459 100644 --- a/packages/super-editor/src/editors/v1/components/popovers/Mentions.vue +++ b/packages/super-editor/src/editors/v1/components/popovers/Mentions.vue @@ -10,7 +10,7 @@ const props = defineProps({ type: String, default: '', }, - inserMention: { + insertMention: { type: Function, required: true, }, @@ -33,7 +33,7 @@ const getFilteredUsers = computed(() => { }); const handleClick = (user) => { - props.inserMention(user); + props.insertMention(user); }; const handleKeydown = (event) => { @@ -50,7 +50,7 @@ const handleKeydown = (event) => { } else if (event.key === 'Enter') { const user = getFilteredUsers.value[activeUserIndex.value]; if (user) { - props.inserMention(user); + props.insertMention(user); } } }; @@ -70,6 +70,7 @@ const handleFocus = () => { >
{ + insertMention: (user) => { const { $from } = this.state.selection; const length = atMention.length; const attributes = { ...user };