fix(ext/popup): don't eat '/' and other keystrokes while typing in inputs
Bug: item-list's global "/" shortcut (focus search) and "+" shortcut (new item) fired even when focus was inside any input/textarea other than the list's own search field. This ate forward-slashes typed into the setup wizard's host-url field and the add/edit form's notes area, and would have done the same for any printable shortcut in a future text field. Root cause: the handler was attached to `document`, stays attached when the user opens an item (and its click-handler navigated without removing the listener), and only excluded the search field by id. Fix: - Add isEditableTarget() helper — returns true for INPUT/TEXTAREA/SELECT and contenteditable elements. Global shortcut handlers bail early when this fires, passing the keystroke through to the field. - Apply the same guard in item-detail.ts (previously only guarded against INPUT, missing TEXTAREA + contenteditable). - Remove handleListKeydown on row-click so it doesn't linger on detail/edit views even before the route-transition keydown listeners install. - Escape in the list view still works from inside an editable field — only the printable-character interceptions are gated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,7 +207,13 @@ function renderLogin(app: HTMLElement, item: Item): void {
|
||||
|
||||
// --- Keyboard shortcuts ---
|
||||
const handler = async (e: KeyboardEvent) => {
|
||||
if ((e.target as HTMLElement).tagName === 'INPUT') return;
|
||||
// Bail if the user is typing into any editable field — don't steal
|
||||
// printable keystrokes meant for an input/textarea/contenteditable element.
|
||||
const t = e.target;
|
||||
if (t instanceof HTMLElement) {
|
||||
const tag = t.tagName;
|
||||
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable) return;
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
|
||||
@@ -97,6 +97,7 @@ export function renderItemList(app: HTMLElement): void {
|
||||
rows.forEach(row => {
|
||||
row.addEventListener('click', async () => {
|
||||
const id = (row as HTMLElement).dataset.id!;
|
||||
document.removeEventListener('keydown', handleListKeydown);
|
||||
await openItem(id);
|
||||
});
|
||||
});
|
||||
@@ -141,11 +142,35 @@ function getFilteredEntries(): Array<[ItemId, ManifestEntry]> {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/// True if the event target is an editable field (input/textarea/contenteditable).
|
||||
/// Global shortcut handlers should bail when the user is typing into a field —
|
||||
/// otherwise printable characters like "/" and "+" get eaten by the shortcut
|
||||
/// routing and never reach the input.
|
||||
function isEditableTarget(target: EventTarget | null): boolean {
|
||||
if (!(target instanceof HTMLElement)) return false;
|
||||
const tag = target.tagName;
|
||||
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
|
||||
if (target.isContentEditable) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleListKeydown(e: KeyboardEvent): void {
|
||||
const state = getState();
|
||||
const target = e.target as HTMLElement;
|
||||
const isSearch = target.id === 'search-input';
|
||||
|
||||
// If the user is typing into any input/textarea (other than the list's own
|
||||
// search field, which we want to focus on "/" even from outside it), let the
|
||||
// keystroke through. The "/" shortcut below is specifically "jump to search
|
||||
// from the list," not "steal printable characters while typing."
|
||||
if (isEditableTarget(target) && !isSearch) {
|
||||
if (e.key === 'Escape') {
|
||||
document.removeEventListener('keydown', handleListKeydown);
|
||||
window.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === '/' && !isSearch) {
|
||||
e.preventDefault();
|
||||
(document.getElementById('search-input') as HTMLInputElement | null)?.focus();
|
||||
|
||||
Reference in New Issue
Block a user