From cef3e6a8055a77cccee89ea7ace248eb5e7c42d3 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Fri, 8 May 2026 10:06:25 +0200 Subject: [PATCH] fix: allow tldraw context menus in tauri --- src/main.tsx | 13 +++++- tests/smoke/tldraw-whiteboard-notes.spec.ts | 50 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main.tsx b/src/main.tsx index 311e852d..99b2b283 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -22,6 +22,7 @@ import { shouldUseLinuxWindowChrome } from './utils/platform' import { reloadFrontendOnceIfStartupFailed } from './utils/frontendReady' const EDITOR_DROP_SELECTOR = '.editor__blocknote-container' +const TLDRAW_CONTEXT_MENU_SELECTOR = '.tldraw-whiteboard' function dataTransferHasFiles(dataTransfer: DataTransfer | null): boolean { if (!dataTransfer) return false @@ -42,6 +43,16 @@ function preventFileDropNavigation(event: DragEvent): void { event.preventDefault() } +function isTldrawContextMenuTarget(target: EventTarget | null): boolean { + return target instanceof Element && target.closest(TLDRAW_CONTEXT_MENU_SELECTOR) !== null +} + +function preventNativeContextMenu(event: MouseEvent): void { + if (isTldrawContextMenuTarget(event.target)) return + + event.preventDefault() +} + document.addEventListener('dragover', preventFileDropNavigation, true) document.addEventListener('drop', preventFileDropNavigation, true) @@ -50,7 +61,7 @@ document.addEventListener('drop', preventFileDropNavigation, true) // Capture phase fires first → prevents native menu; React bubble phase still fires // → our custom context menus (e.g. sidebar right-click) work correctly. if ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) { - document.addEventListener('contextmenu', (e) => e.preventDefault(), true) + document.addEventListener('contextmenu', preventNativeContextMenu, true) } if (shouldUseLinuxWindowChrome()) { diff --git a/tests/smoke/tldraw-whiteboard-notes.spec.ts b/tests/smoke/tldraw-whiteboard-notes.spec.ts index 930c6ee6..2c7d6d9e 100644 --- a/tests/smoke/tldraw-whiteboard-notes.spec.ts +++ b/tests/smoke/tldraw-whiteboard-notes.spec.ts @@ -6,6 +6,14 @@ import { executeCommand, openCommandPalette } from './helpers' let tempVaultDir: string +type TauriHarnessWindow = Window & typeof globalThis & { + isTauri?: boolean + __TAURI__?: unknown + __TAURI_INTERNALS__?: { + invoke?: () => Promise + } +} + const WHITEBOARD_NOTE = [ '# Whiteboard Embed', '', @@ -18,11 +26,15 @@ const WHITEBOARD_NOTE = [ 'Context after the board.', '', ].join('\n') +const TAURI_CONTEXT_MENU_TEST = 'embedded tldraw context menu opens from a native right-click path' test.beforeEach(async ({ page }, testInfo) => { testInfo.setTimeout(90_000) tempVaultDir = createFixtureVaultCopy() fs.writeFileSync(path.join(tempVaultDir, 'note', 'whiteboard-embed.md'), WHITEBOARD_NOTE) + if (testInfo.title === TAURI_CONTEXT_MENU_TEST) { + await installTauriContextMenuHarness(page) + } await openFixtureVault(page, tempVaultDir) }) @@ -35,6 +47,20 @@ async function openNote(page: Page, title: string): Promise { await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5_000 }) } +async function installTauriContextMenuHarness(page: Page): Promise { + await page.addInitScript(() => { + const harnessWindow = window as TauriHarnessWindow + harnessWindow.isTauri = false + harnessWindow.__TAURI__ = harnessWindow.__TAURI__ ?? {} + harnessWindow.__TAURI_INTERNALS__ = { + ...harnessWindow.__TAURI_INTERNALS__, + invoke: async () => { + throw new Error('No native bridge in context menu harness') + }, + } + }) +} + async function toggleRawMode(page: Page, visibleSelector: '.bn-editor' | '.cm-content'): Promise { await openCommandPalette(page) await executeCommand(page, 'Toggle Raw') @@ -180,6 +206,30 @@ test('embedded tldraw dialogs appear and release focus when closed', async ({ pa await expectNoEditorNodeSelection(page) }) +test(TAURI_CONTEXT_MENU_TEST, async ({ page }) => { + await openNote(page, 'Whiteboard Embed') + + const whiteboard = page.locator('.tldraw-whiteboard') + await expect(whiteboard).toBeVisible({ timeout: 20_000 }) + const boardBox = await whiteboard.boundingBox() + expect(boardBox).not.toBeNull() + + const canvas = page.getByTestId('canvas') + await canvas.click({ position: { x: 160, y: 160 } }) + await canvas.click({ button: 'right', position: { x: 160, y: 160 } }) + + const contextMenu = page.getByTestId('context-menu') + await expect(contextMenu).toBeVisible({ timeout: 5_000 }) + const menuBox = await contextMenu.boundingBox() + expect(menuBox).not.toBeNull() + expect(menuBox!.x).toBeGreaterThanOrEqual(boardBox!.x - 1) + expect(menuBox!.x + menuBox!.width).toBeLessThanOrEqual(boardBox!.x + boardBox!.width + 1) + await expectNoEditorNodeSelection(page) + + await page.keyboard.press('Escape') + await expect(contextMenu).toHaveCount(0) +}) + test('embedded tldraw drawing uses the clicked coordinates while zoomed', async ({ page }) => { await openNote(page, 'Whiteboard Embed') await applyZoom(page, 110)