diff --git a/src/components/TldrawWhiteboard.tsx b/src/components/TldrawWhiteboard.tsx
index b0843621..c5778fdf 100644
--- a/src/components/TldrawWhiteboard.tsx
+++ b/src/components/TldrawWhiteboard.tsx
@@ -1,21 +1,16 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type MutableRefObject, type PointerEvent as ReactPointerEvent } from 'react'
import { getAssetUrlsByImport } from '@tldraw/assets/imports.vite'
+import { Dialog as DialogPrimitive } from 'radix-ui'
import {
Box,
- DefaultKeyboardShortcutsDialog,
- DefaultKeyboardShortcutsDialogContent,
- TldrawUiButton,
- TldrawUiButtonIcon,
- TldrawUiMenuContextProvider,
Tldraw,
createTLStore,
defaultUserPreferences,
getSnapshot,
loadSnapshot,
- react as reactToTldrawSignal,
useDialogs,
useTldrawUser,
- useTranslation,
+ useValue,
type Editor,
type TLEventInfo,
type TLUiDialog,
@@ -35,7 +30,6 @@ function resolveTldrawAssetUrl(assetUrl: string | undefined): string {
}
const tldrawAssetUrls = getAssetUrlsByImport(resolveTldrawAssetUrl)
-const tldrawUiComponents = { Dialogs: TolariaTldrawDialogs }
interface TldrawWhiteboardProps {
boardId: string
@@ -221,43 +215,7 @@ interface TolariaTldrawDialogProps {
}
const DIALOG_OPEN_DISMISS_GRACE_MS = 250
-
-function isKeyboardShortcutsDialog(dialog: TLUiDialog): boolean {
- return dialog.component === DefaultKeyboardShortcutsDialog
-}
-
-interface TolariaKeyboardShortcutsDialogProps {
- onClose: () => void
-}
-
-function TolariaKeyboardShortcutsDialog({ onClose }: TolariaKeyboardShortcutsDialogProps) {
- const msg = useTranslation()
-
- return (
- <>
-
-
{msg('shortcuts-dialog.title')}
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )
-}
+let retainedTolariaTldrawDialogs: TLUiDialog[] = []
function useDeferredDialogOpen() {
const openedAtRef = useRef(0)
@@ -292,14 +250,12 @@ function shouldCloseFromOverlayClick(
interface TolariaTldrawDialogContentProps {
dialog: TLUiDialog
- dialogTitle: string | undefined
mouseDownInsideContentRef: MutableRefObject
onClose: () => void
}
function TolariaTldrawDialogContent({
dialog,
- dialogTitle,
mouseDownInsideContentRef,
onClose,
}: TolariaTldrawDialogContentProps) {
@@ -314,14 +270,17 @@ function TolariaTldrawDialogContent({
dir="ltr"
className="tlui-dialog__content"
aria-describedby={undefined}
- aria-label={dialogTitle}
role="dialog"
+ onKeyDown={(event) => {
+ if (event.key !== 'Escape') return
+ event.preventDefault()
+ mouseDownInsideContentRef.current = false
+ onClose()
+ }}
onMouseDown={() => { mouseDownInsideContentRef.current = true }}
onMouseUp={() => { mouseDownInsideContentRef.current = false }}
>
- {isKeyboardShortcutsDialog(dialog)
- ?
- : }
+
)
}
@@ -329,57 +288,64 @@ function TolariaTldrawDialogContent({
const TolariaTldrawDialog = memo(function TolariaTldrawDialog({ dialog, onClose }: TolariaTldrawDialogProps) {
const mouseDownInsideContentRef = useRef(false)
const { openedAtRef, readyToOpen } = useDeferredDialogOpen()
- const msg = useTranslation()
- const dialogTitle = isKeyboardShortcutsDialog(dialog) ? msg('shortcuts-dialog.title') : undefined
- const closeDialog = useCallback(() => {
+ const closeDialogFromBackground = useCallback(() => {
if (!canDismissDialog(openedAtRef.current)) return
onClose(dialog.id)
}, [dialog.id, onClose, openedAtRef])
const closeDialogNow = useCallback(() => { onClose(dialog.id) }, [dialog.id, onClose])
+ const handleOpenChange = useCallback((isOpen: boolean) => {
+ if (!isOpen) closeDialogNow()
+ }, [closeDialogNow])
if (!readyToOpen) return null
return (
- {
- if (event.target === event.currentTarget) mouseDownInsideContentRef.current = false
- }}
- onClick={(event) => {
- if (shouldCloseFromOverlayClick(event, dialog, mouseDownInsideContentRef.current)) closeDialog()
- }}
- >
-
-
+
+ {
+ if (event.target === event.currentTarget) mouseDownInsideContentRef.current = false
+ }}
+ onClick={(event) => {
+ if (shouldCloseFromOverlayClick(event, dialog, mouseDownInsideContentRef.current)) {
+ closeDialogFromBackground()
+ }
+ }}
+ >
+
+
+
)
})
function TolariaTldrawDialogs() {
const { dialogs, removeDialog } = useDialogs()
- const [visibleDialogs, setVisibleDialogs] = useState(() => dialogs.get())
+ const requestedDialogs = useValue('tolaria tldraw dialogs', () => dialogs.get(), [dialogs])
+ const [visibleDialogs, setVisibleDialogs] = useState(() =>
+ retainedTolariaTldrawDialogs.length > 0 ? retainedTolariaTldrawDialogs : dialogs.get()
+ )
const closeVisibleDialog = useCallback((id: string) => {
- setVisibleDialogs((current) => current.filter((dialog) => dialog.id !== id))
+ const nextDialogs = retainedTolariaTldrawDialogs.filter((dialog) => dialog.id !== id)
+ retainedTolariaTldrawDialogs = nextDialogs
+ setVisibleDialogs(nextDialogs)
removeDialog(id)
}, [removeDialog])
- useEffect(() => reactToTldrawSignal(
- 'tolaria tldraw dialogs',
- () => {
- const nextDialogs = dialogs.get()
- if (nextDialogs.length > 0) {
- // tldraw clears the dialog atom while Radix closes the menu; keep the last requested dialog mounted locally.
- setVisibleDialogs(nextDialogs)
- }
- }
- ), [dialogs])
+ useEffect(() => {
+ if (requestedDialogs.length === 0) return
+ // tldraw clears the dialog atom while Radix closes the menu; keep the last requested dialog mounted locally.
+ retainedTolariaTldrawDialogs = requestedDialogs
+ queueMicrotask(() => {
+ setVisibleDialogs(requestedDialogs)
+ })
+ }, [requestedDialogs])
return visibleDialogs.map((dialog) => (
({ Dialogs: TolariaTldrawDialogs }), [])
useEffect(() => {
onSnapshotChangeRef.current = onSnapshotChange
diff --git a/tests/smoke/tldraw-whiteboard-notes.spec.ts b/tests/smoke/tldraw-whiteboard-notes.spec.ts
index ca537c42..a171defe 100644
--- a/tests/smoke/tldraw-whiteboard-notes.spec.ts
+++ b/tests/smoke/tldraw-whiteboard-notes.spec.ts
@@ -226,6 +226,28 @@ test('embedded tldraw dialogs appear and release focus when closed', async ({ pa
await expectNoEditorNodeSelection(page)
})
+test('embedded tldraw insert embed dialog opens without crashing the note', async ({ page }) => {
+ await openNote(page, 'Whiteboard Embed')
+
+ const whiteboard = page.locator('.tldraw-whiteboard')
+ await expect(whiteboard).toBeVisible({ timeout: 20_000 })
+
+ await page.getByTestId('main-menu.button').click()
+ await page.getByTestId('main-menu.insert-embed').click()
+
+ const embedDialog = page.locator('.tldraw-whiteboard .tlui-dialog__content')
+ await expect(embedDialog).toBeVisible({ timeout: 5_000 })
+ await expect(embedDialog).toContainText('Insert embed')
+ await expect(page.locator('.error-boundary')).toHaveCount(0)
+
+ const dialogBox = await embedDialog.boundingBox()
+ const boardBox = await whiteboard.boundingBox()
+ expect(dialogBox).not.toBeNull()
+ expect(boardBox).not.toBeNull()
+ expect(dialogBox!.x).toBeGreaterThanOrEqual(boardBox!.x)
+ expect(dialogBox!.x + dialogBox!.width).toBeLessThanOrEqual(boardBox!.x + boardBox!.width)
+})
+
test(TAURI_CONTEXT_MENU_TEST, async ({ page }) => {
await openNote(page, 'Whiteboard Embed')