Fix live page editable focus handling (#256)

This commit is contained in:
Paul Bakaus
2026-06-16 19:59:48 -07:00
committed by GitHub
parent b86f2cc353
commit 99a284a0d9
2 changed files with 54 additions and 1 deletions
+20 -1
View File
@@ -6489,10 +6489,13 @@
) {
return;
}
if (isPageEditableElement(deepActive) && !isInlineEditActive(deepActive)) {
return;
}
// While a contenteditable text-leaf is focused, let the browser handle
// all keys except Escape. Escape cancels the current edit (restores
// original text) and blurs without saving, staying in CONFIGURING.
if (e.target.isContentEditable && inlineEditRows.some((r) => r.el === e.target)) {
if (e.target.isContentEditable && isInlineEditActive(e.target)) {
if (e.key !== 'Escape') return;
e.preventDefault();
e.stopPropagation();
@@ -8312,6 +8315,21 @@ void main() {
&& !steerLocked;
}
function isPageEditableElement(el) {
if (!el || own(el)) return false;
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName || '')) return true;
return !!el.isContentEditable;
}
function isInlineEditActive(el) {
return !!el && inlineEditRows.some((r) => r.el === el);
}
function isPageEditableActive() {
const active = activeElementDeep();
return isPageEditableElement(active) && !isInlineEditActive(active);
}
function pageHasHostTextSelection() {
const sel = window.getSelection?.();
if (!sel || sel.isCollapsed) return false;
@@ -8325,6 +8343,7 @@ void main() {
function shouldSteerAutoFocus() {
return shouldFocusSteerChat()
&& !steerFocusSuspended
&& !isPageEditableActive()
&& performance.now() >= steerFocusPauseUntil;
}
+34
View File
@@ -154,6 +154,24 @@ describe('live-browser.js regression guards', () => {
);
});
it('does not autofocus the steering chat while a page editable is focused', () => {
assert.match(
SOURCE,
/function isPageEditableElement\(el\) \{[\s\S]{0,160}?own\(el\)[\s\S]{0,160}?\^\(INPUT\|TEXTAREA\|SELECT\)\$[\s\S]{0,80}?el\.isContentEditable/,
'page-owned inputs, textareas, selects, and contenteditables must be recognized before steer focus recovery runs',
);
assert.match(
SOURCE,
/function isPageEditableActive\(\) \{[\s\S]{0,120}?activeElementDeep\(\)[\s\S]{0,120}?isPageEditableElement\(active\) && !isInlineEditActive\(active\)/,
'auto-focus recovery must check the deep active element instead of only host text selection',
);
assert.match(
SOURCE,
/function shouldSteerAutoFocus\(\) \{[\s\S]{0,160}?&& !isPageEditableActive\(\)/,
'steer chat auto-focus must back off while the page owns an editable caret',
);
});
it('pins edit badge button metrics instead of inheriting host button chrome', () => {
const start = SOURCE.indexOf('const calloutStyle = (color, borderColor) => ({');
const end = SOURCE.indexOf(' });', start);
@@ -345,6 +363,22 @@ describe('live-browser.js regression guards', () => {
);
});
it('lets page editables keep Enter and arrow keydown events', () => {
const start = SOURCE.indexOf('function handleKeyDown(e)');
const pendingApplyStart = SOURCE.indexOf('if (pendingApplyInFlight)', start);
const guardSource = SOURCE.slice(start, pendingApplyStart);
assert.match(
guardSource,
/isPageEditableElement\(deepActive\) && !isInlineEditActive\(deepActive\)[\s\S]{0,40}?return;/,
'page-owned editables must short-circuit global key handling before variant navigation or accept handling',
);
assert.match(
guardSource,
/e\.target\.isContentEditable && isInlineEditActive\(e\.target\)/,
'impeccable inline edit rows must keep their existing Escape-cancel path',
);
});
it('configure input Escape tears down annotation overlay before returning to picking', () => {
// The configure prompt auto-focuses. While focused, the global keydown
// handler bails on own() inputs, so this local Escape path must hide the