mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
feat(app): close settings with Escape (#2828)
* feat(app): close settings with Escape * test(app): cover settings Escape overlay ownership --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
paseo-bot[bot]
parent
bffb33ed32
commit
f73f65cd78
@@ -121,6 +121,41 @@ test.describe("Settings sidebar navigation", () => {
|
||||
await clickSettingsBackToWorkspace(page);
|
||||
await expect(page).not.toHaveURL(/\/settings(\/|$)/);
|
||||
});
|
||||
|
||||
test("pressing Escape closes settings", async ({ page }) => {
|
||||
await gotoAppShell(page);
|
||||
await openSettings(page);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page).not.toHaveURL(/\/settings(\/|$)/);
|
||||
});
|
||||
|
||||
test("Escape lets settings dropdowns and modals close before leaving settings", async ({
|
||||
page,
|
||||
}) => {
|
||||
await gotoAppShell(page);
|
||||
await openSettings(page);
|
||||
|
||||
await test.step("a dropdown owns Escape", async () => {
|
||||
await openSettingsSection(page, "appearance");
|
||||
await page.getByLabel(/Theme:/).click();
|
||||
await expect(page.getByRole("menuitem", { name: "System", exact: true })).toBeVisible();
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await expect(page.getByRole("menuitem", { name: "System", exact: true })).toHaveCount(0);
|
||||
await expect(page).toHaveURL(/\/settings(\/|$)/);
|
||||
});
|
||||
|
||||
await test.step("a modal owns Escape", async () => {
|
||||
await openAddHostFlow(page);
|
||||
await expect(page.getByText("Add connection", { exact: true })).toBeVisible();
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await expect(page.getByText("Add connection", { exact: true })).toHaveCount(0);
|
||||
await expect(page).toHaveURL(/\/settings(\/|$)/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Settings — compact master-detail", () => {
|
||||
|
||||
@@ -32,6 +32,8 @@ import { isNative } from "@/constants/platform";
|
||||
import { keyboardShortcutsAvailable } from "@/keyboard/availability";
|
||||
import { getDesktopHost, isElectronRuntime } from "@/desktop/host";
|
||||
import { isImeComposingKeyboardEvent } from "@/utils/keyboard-ime";
|
||||
import { buildOpenProjectRoute } from "@/utils/host-routes";
|
||||
import { hasActiveWebOverlay } from "@/lib/overlay-root";
|
||||
import {
|
||||
type ActiveWorkspaceSelection,
|
||||
navigateToLastWorkspace,
|
||||
@@ -179,7 +181,11 @@ export function useKeyboardShortcuts({
|
||||
navigateToWorkspace({ serverId: action.serverId, workspaceId: action.workspaceId });
|
||||
return true;
|
||||
case "navigate-last-workspace":
|
||||
return navigateToLastWorkspace();
|
||||
if (navigateToLastWorkspace()) {
|
||||
return true;
|
||||
}
|
||||
router.replace(buildOpenProjectRoute());
|
||||
return true;
|
||||
case "router-replace":
|
||||
router.replace(action.route as Parameters<typeof router.replace>[0]);
|
||||
return true;
|
||||
@@ -330,6 +336,14 @@ export function useKeyboardShortcuts({
|
||||
}
|
||||
|
||||
const key = event.key ?? "";
|
||||
if (
|
||||
key === "Escape" &&
|
||||
pathname.startsWith("/settings") &&
|
||||
!isMobile &&
|
||||
hasActiveWebOverlay()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (key === badgeModifierKey && !event.shiftKey) {
|
||||
setBadgeModifierDown(true);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,27 @@ describe("routeKeyboardShortcut — dispatch passthroughs", () => {
|
||||
action: expected,
|
||||
});
|
||||
});
|
||||
|
||||
it("closes desktop settings when Escape routes through agent interrupt", () => {
|
||||
expect(
|
||||
routeKeyboardShortcut(
|
||||
{ action: "agent.interrupt", payload: null },
|
||||
makeCtx({ pathname: "/settings/general" }),
|
||||
),
|
||||
).toEqual<ShortcutAction>({ kind: "navigate-last-workspace" });
|
||||
});
|
||||
|
||||
it("keeps agent interrupt behavior on compact settings layouts", () => {
|
||||
expect(
|
||||
routeKeyboardShortcut(
|
||||
{ action: "agent.interrupt", payload: null },
|
||||
makeCtx({ pathname: "/settings/general", isMobile: true }),
|
||||
),
|
||||
).toEqual<ShortcutAction>({
|
||||
kind: "dispatch",
|
||||
action: { id: "agent.interrupt", scope: "global" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("routeKeyboardShortcut — workspace.tab.navigate", () => {
|
||||
|
||||
@@ -177,6 +177,13 @@ export function routeKeyboardShortcut(
|
||||
): ShortcutAction {
|
||||
const passthrough = PASSTHROUGH_DISPATCH[input.action];
|
||||
if (passthrough) {
|
||||
if (
|
||||
input.action === "agent.interrupt" &&
|
||||
ctx.pathname.startsWith("/settings") &&
|
||||
!ctx.isMobile
|
||||
) {
|
||||
return { kind: "navigate-last-workspace" };
|
||||
}
|
||||
return dispatch(passthrough);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,10 @@ function getTopWebOverlay(): WebOverlayEntry | undefined {
|
||||
}, undefined);
|
||||
}
|
||||
|
||||
export function hasActiveWebOverlay(): boolean {
|
||||
return getTopWebOverlay() !== undefined;
|
||||
}
|
||||
|
||||
function getFocusableElements(scope: HTMLElement): HTMLElement[] {
|
||||
const selector = [
|
||||
"a[href]",
|
||||
|
||||
Reference in New Issue
Block a user