diff --git a/packages/chess/e2e/layouts.spec.ts b/packages/chess/e2e/layouts.spec.ts index 725ff8c..b1f616b 100644 --- a/packages/chess/e2e/layouts.spec.ts +++ b/packages/chess/e2e/layouts.spec.ts @@ -52,7 +52,9 @@ test.describe('LayoutPicker (lobby)', () => { test('selecting Dunsany updates the description', async ({ page }) => { const picker = page.getByTestId('layout-picker'); await picker.selectOption('dunsany'); - await expect(page.locator('text=/Dunsany/i').first()).toBeVisible(); + await expect( + page.getByTestId('layout-picker-description'), + ).toContainText(/pawn tide/i); }); test('?layoutId=dunsany query param pre-selects the layout', async ({ page }) => { diff --git a/packages/chess/src/ui/LayoutEditor.tsx b/packages/chess/src/ui/LayoutEditor.tsx index ec567ae..960f232 100644 --- a/packages/chess/src/ui/LayoutEditor.tsx +++ b/packages/chess/src/ui/LayoutEditor.tsx @@ -29,7 +29,7 @@ * incrementally — simpler and avoids ambiguity about which source * of truth wins). */ -import { useMemo, useState, type ReactNode } from 'react'; +import { useEffect, useMemo, useState, type ReactNode } from 'react'; import { toast } from 'sonner'; import { fromFen, @@ -92,6 +92,19 @@ export function LayoutEditor({ const [libraryOpen, setLibraryOpen] = useState(false); const [libraryVersion, setLibraryVersion] = useState(0); // bump to force reload + // Global Esc listener: close the editor regardless of where focus + // currently is. We can't rely on the modal div's onKeyDown because + // the user might have focused a textarea / button inside a nested + // panel, and keyboard events on those don't bubble to the modal + // root in a way that works universally. + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [onClose]); + // Derived FEN whenever placements change — keeps the readonly view // in sync. The user's in-flight fenDraft is a SEPARATE state so // typing doesn't get clobbered by a re-render. diff --git a/packages/chess/src/ui/LayoutPicker.tsx b/packages/chess/src/ui/LayoutPicker.tsx index a1cf49a..1f7aaf1 100644 --- a/packages/chess/src/ui/LayoutPicker.tsx +++ b/packages/chess/src/ui/LayoutPicker.tsx @@ -116,7 +116,10 @@ export function LayoutPicker({ -
+
{isCustom ? 'Custom layout loaded from your editor.' : value.description}
{value.suggestedPresets !== undefined &&