From 89b85f58d58c0e9426338921fc1654c56206c3fd Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 23 Oct 2026 20:37:30 +0200 Subject: [PATCH] fix(19.7): RB-IN-04 document that a focused widget host mutes the plain keys The custom-element-in-form rule that keeps Esc, / and ? out of a plugin field type's closed shadow root also applies to a focusable widget that is not an input. The header comment and isEditableTarget now state the trade-off and why the rule is not narrowed to plugin field types. --- admin/src/app/hotkeys.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/admin/src/app/hotkeys.ts b/admin/src/app/hotkeys.ts index a4e5ee58..59769ace 100644 --- a/admin/src/app/hotkeys.ts +++ b/admin/src/app/hotkeys.ts @@ -23,6 +23,13 @@ // dismissable layer (dialog, popover, dropdown, select, link popover) is // open: those close on Esc themselves without calling preventDefault, so the // DOM is checked for their data-dismissable-layer attribute instead. +// +// A focused custom element (a tag name with "-") inside a form counts as an +// editable control, because a closed shadow root hides whether it holds an +// input. The trade-off: a focusable widget that is not an input (a type: +// widget chart or button strip) also mutes Esc, / and ? while it has focus; +// they work again once focus leaves it. Narrowing the rule to plugin field +// types would let Esc leave the form from a widget with an input inside. import { readonly, ref } from 'vue' import { t } from './i18n' @@ -100,7 +107,8 @@ export function comboOf(event: KeyboardEvent): HotkeyCombo | null { * True when the target is inside an input, textarea, select or contenteditable * element, or is a custom element (a tag name with "-") inside a form: a * plugin field type whose closed shadow root hides the real input, where - * composedPath() stops at the host. + * composedPath() stops at the host. Any such element counts, a focusable + * non-input widget included (see the header comment). */ export function isEditableTarget(target: EventTarget | null): boolean { if (!target || typeof (target as Element).closest !== 'function') {