Skip to content

Commit 4f71136

Browse files
committed
fix(webapp): stop Esc in a text field from closing panels
The Esc close shortcuts are registered on `document`, so `enabledOnInputElements: true` made them fire from every input on the page, not just fields inside the panel. Typing in a page-level search box or the chat composer and pressing Esc closed the panel behind it. The composer and SearchInput tried to guard against this with `stopPropagation` on the React `onKeyDown`, which cannot work: the app hydrates into `document`, so React's delegated listener and the hotkey listener are siblings on the same node and only `stopImmediatePropagation` would stop the other one. Drop the flag from the nine Esc close shortcuts instead, so the focused field handles Esc itself: it clears a search box or leaves the composer with the draft intact, and a second Esc closes the panel. Verified in a browser against the seeded References org: Esc from a page-level search box or a composer with a draft no longer closes the chat panel, a second Esc does, and Esc still closes dialogs, sheets, detail panels, and closes only an open dropdown when one is layered over a panel. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 44973d1 commit 4f71136

13 files changed

Lines changed: 19 additions & 15 deletions

File tree

.server-changes/esc-to-close-fixes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: fix
44
---
55

6-
Esc now closes the authenticator app setup window, the chat panel, and detail panels while you're typing in one of their fields. Panels that hold an editor or a form you're filling in keep what you've typed instead of closing, and clicking outside the authenticator app setup window no longer cancels the setup. Every side sheet now shows its Esc shortcut, closing a dialog with Esc no longer also closes the panel behind it, and the Vercel setup modal no longer shows a close button it ignores.
6+
Esc now closes the authenticator app setup window, the chat panel and detail panels, and clicking outside the authenticator app setup window no longer cancels the setup. While you're typing in a field, Esc leaves that field first — clearing a search box, or keeping an unsent message — so a second Esc closes the panel and a stray Esc no longer discards what you typed or shuts a panel you were working in. Every side sheet now shows its Esc shortcut, closing a dialog with Esc no longer also closes the panel behind it, and the Vercel setup modal no longer shows a close button it ignores.

apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export function DashboardAgentComposer({
3131
e.preventDefault();
3232
onSubmit();
3333
}
34-
// Keep an unsent message: Esc blurs first, so only a second Esc closes the panel.
35-
if (e.key === "Escape" && value.trim().length > 0) {
36-
e.stopPropagation();
34+
// Esc leaves the composer, so a second Esc closes the panel and an
35+
// unsent message survives the first one.
36+
if (e.key === "Escape") {
3737
e.currentTarget.blur();
3838
}
3939
}}

apps/webapp/app/components/dashboard-agent/DashboardAgentHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function DashboardAgentHeader({
2828
onClick={onClose}
2929
variant="minimal/small"
3030
TrailingIcon={XMarkIcon}
31-
shortcut={{ key: "esc", enabledOnInputElements: true }}
31+
shortcut={{ key: "esc" }}
3232
shortcutPosition="before-trailing-icon"
3333
className="pl-1"
3434
aria-label="Close"

apps/webapp/app/components/logs/LogDetailView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export function LogDetailView({ logId, initialLog, onClose, searchTerm }: LogDet
117117
onClick={onClose}
118118
variant="minimal/small"
119119
TrailingIcon={ExitIcon}
120-
shortcut={{ key: "esc", enabledOnInputElements: true }}
120+
shortcut={{ key: "esc" }}
121121
shortcutPosition="before-trailing-icon"
122122
className="pl-1"
123123
/>
@@ -138,7 +138,7 @@ export function LogDetailView({ logId, initialLog, onClose, searchTerm }: LogDet
138138
onClick={onClose}
139139
variant="minimal/small"
140140
TrailingIcon={ExitIcon}
141-
shortcut={{ key: "esc", enabledOnInputElements: true }}
141+
shortcut={{ key: "esc" }}
142142
shortcutPosition="before-trailing-icon"
143143
className="pl-1"
144144
/>

apps/webapp/app/components/primitives/SearchInput.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ export function SearchInput({
127127
}
128128
if (e.key === "Escape") {
129129
if (text.length > 0) {
130-
e.stopPropagation();
131130
handleClear();
132131
} else {
133132
e.currentTarget.blur();

apps/webapp/app/hooks/useShortcutKeys.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ type useShortcutKeysProps = {
2323
shortcut: ShortcutDefinition | undefined;
2424
action: (event: KeyboardEvent) => void;
2525
disabled?: boolean;
26+
/**
27+
* Shortcuts are registered on `document`, so this fires the shortcut from every
28+
* input on the page, not just the ones near it. Don't set it on an Escape
29+
* close shortcut: let the focused field handle Escape and close on the second press.
30+
*/
2631
enabledOnInputElements?: boolean;
2732
/**
2833
* The element this shortcut belongs to. When set, an Escape shortcut is ignored

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ function NewTaskPromptsPanel({ onClose }: { onClose: () => void }) {
844844
onClick={onClose}
845845
variant="minimal/small"
846846
TrailingIcon={ExitIcon}
847-
shortcut={{ key: "esc", enabledOnInputElements: true }}
847+
shortcut={{ key: "esc" }}
848848
shortcutPosition="before-trailing-icon"
849849
className="pl-1"
850850
/>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.batches.$batchParam/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export default function Page() {
9696
to={v3BatchesPath(organization, project, environment)}
9797
variant="minimal/small"
9898
TrailingIcon={ExitIcon}
99-
shortcut={{ key: "esc", enabledOnInputElements: true }}
99+
shortcut={{ key: "esc" }}
100100
shortcutPosition="before-trailing-icon"
101101
className="pl-1"
102102
/>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export default function Page() {
183183
to={v3BulkActionsPath(organization, project, environment)}
184184
variant="minimal/small"
185185
TrailingIcon={ExitIcon}
186-
shortcut={{ key: "esc", enabledOnInputElements: true }}
186+
shortcut={{ key: "esc" }}
187187
shortcutPosition="before-trailing-icon"
188188
className="pl-1"
189189
/>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export default function Page() {
340340
}`}
341341
variant="minimal/small"
342342
TrailingIcon={ExitIcon}
343-
shortcut={{ key: "esc", enabledOnInputElements: true }}
343+
shortcut={{ key: "esc" }}
344344
shortcutPosition="before-trailing-icon"
345345
className="pl-1"
346346
/>

0 commit comments

Comments
 (0)