From 7f1846ed47df2857d562e9d966e81b1dae216b25 Mon Sep 17 00:00:00 2001 From: Minwook Shin <163576506+minwookshin@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:03:04 -0400 Subject: [PATCH] fix(toolbar): preserve selection inside modal dialogs --- packages/quill/src/modules/toolbar.ts | 6 +++ packages/quill/test/e2e/toolbar.spec.ts | 49 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 packages/quill/test/e2e/toolbar.spec.ts diff --git a/packages/quill/src/modules/toolbar.ts b/packages/quill/src/modules/toolbar.ts index a178d25936..87dc70f46b 100644 --- a/packages/quill/src/modules/toolbar.ts +++ b/packages/quill/src/modules/toolbar.ts @@ -87,6 +87,12 @@ class Toolbar extends Module { debug.warn('ignoring attaching to nonexistent format', format, input); return; } + if (input.tagName === 'BUTTON') { + input.addEventListener('mousedown', (event) => { + // Keep the editor selection when a button is clicked inside a modal dialog. + if (event.button === 0) event.preventDefault(); + }); + } const eventName = input.tagName === 'SELECT' ? 'change' : 'click'; input.addEventListener(eventName, (e) => { let value; diff --git a/packages/quill/test/e2e/toolbar.spec.ts b/packages/quill/test/e2e/toolbar.spec.ts new file mode 100644 index 0000000000..442bb53593 --- /dev/null +++ b/packages/quill/test/e2e/toolbar.spec.ts @@ -0,0 +1,49 @@ +import { expect } from '@playwright/test'; +import { test } from './fixtures/index.js'; + +for (const modal of [false, true]) { + test(`toolbar preserves a selected range in a ${modal ? 'modal' : 'non-modal'} dialog`, async ({ + page, + editorPage, + }) => { + await editorPage.open(); + await page.evaluate((isModal) => { + const dialog = document.createElement('dialog'); + dialog.appendChild(document.querySelector('#standalone-container')!); + document.body.appendChild(dialog); + if (isModal) dialog.showModal(); + else dialog.show(); + }, modal); + await editorPage.root.fill('Hello world'); + await editorPage.selectText('Hello'); + + const bold = page.locator('.ql-bold'); + for (const formatted of [true, false]) { + await bold.hover(); + await page.mouse.down(); + // Hold the button as in a slow click, allowing selectionchange to run. + await page.waitForTimeout(150); + expect( + await page.evaluate(() => document.getSelection()?.toString()), + ).toBe('Hello'); + await page.mouse.up(); + + await expect(editorPage.root).toHaveJSProperty( + 'innerHTML', + formatted + ? '

Hello world

' + : '

Hello world

', + ); + expect(await editorPage.getSelection()).toEqual({ index: 0, length: 5 }); + } + + await bold.focus(); + await page.keyboard.press('Enter'); + await expect(editorPage.root).toHaveJSProperty( + 'innerHTML', + '

Hello world

', + ); + await expect(editorPage.root).toBeFocused(); + expect(await editorPage.getSelection()).toEqual({ index: 0, length: 5 }); + }); +}