From f397b9f1230996d6fece5cfa6daddb3995c680cf Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Mon, 13 Apr 2026 16:31:03 -0700 Subject: [PATCH] Fix keyboard nav and click-to-deselect in live mode picker - Arrow keys now pass through to element picker when the freeform input is empty, instead of being swallowed by stopPropagation - Arrow nav works in both PICKING and CONFIGURING states, so you can change your element selection while the config bar is open - Clicking outside the selected element and bar returns to PICKING mode, matching the expected deselect behavior Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .pi/skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- .../skills/impeccable/scripts/live-browser.js | 38 ++++++++++++++----- 12 files changed, 348 insertions(+), 108 deletions(-) diff --git a/.agents/skills/impeccable/scripts/live-browser.js b/.agents/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.agents/skills/impeccable/scripts/live-browser.js +++ b/.agents/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.claude/skills/impeccable/scripts/live-browser.js b/.claude/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.claude/skills/impeccable/scripts/live-browser.js +++ b/.claude/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.cursor/skills/impeccable/scripts/live-browser.js b/.cursor/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.cursor/skills/impeccable/scripts/live-browser.js +++ b/.cursor/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.gemini/skills/impeccable/scripts/live-browser.js b/.gemini/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.gemini/skills/impeccable/scripts/live-browser.js +++ b/.gemini/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.github/skills/impeccable/scripts/live-browser.js b/.github/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.github/skills/impeccable/scripts/live-browser.js +++ b/.github/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.kiro/skills/impeccable/scripts/live-browser.js b/.kiro/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.kiro/skills/impeccable/scripts/live-browser.js +++ b/.kiro/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.opencode/skills/impeccable/scripts/live-browser.js b/.opencode/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.opencode/skills/impeccable/scripts/live-browser.js +++ b/.opencode/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.pi/skills/impeccable/scripts/live-browser.js b/.pi/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.pi/skills/impeccable/scripts/live-browser.js +++ b/.pi/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.rovodev/skills/impeccable/scripts/live-browser.js b/.rovodev/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.rovodev/skills/impeccable/scripts/live-browser.js +++ b/.rovodev/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.trae-cn/skills/impeccable/scripts/live-browser.js b/.trae-cn/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.trae-cn/skills/impeccable/scripts/live-browser.js +++ b/.trae-cn/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/.trae/skills/impeccable/scripts/live-browser.js b/.trae/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/.trae/skills/impeccable/scripts/live-browser.js +++ b/.trae/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } diff --git a/source/skills/impeccable/scripts/live-browser.js b/source/skills/impeccable/scripts/live-browser.js index 75bb26d94..59e92d239 100644 --- a/source/skills/impeccable/scripts/live-browser.js +++ b/source/skills/impeccable/scripts/live-browser.js @@ -342,9 +342,11 @@ input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { - e.stopPropagation(); // Don't trigger element picker keyboard nav - if (e.key === 'Enter') { e.preventDefault(); handleGo(); } - if (e.key === 'Escape') { e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; } + if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } + if (e.key === 'Escape') { e.stopPropagation(); e.preventDefault(); input.blur(); hideBar(); state = 'PICKING'; return; } + // Let arrow keys pass through to the element picker when the input is empty + if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return; + e.stopPropagation(); }); row.appendChild(input); @@ -970,6 +972,15 @@ if (pickerEl?.style.display !== 'none' && !own(e.target)) { hideActionPicker(); } + // In CONFIGURING: click outside the bar and selected element returns to PICKING + if (state === 'CONFIGURING' && !own(e.target) && selectedElement && !selectedElement.contains(e.target)) { + hideBar(); + stopScrollTracking(); + state = 'PICKING'; + hoveredElement = null; + hideHighlight(); + return; + } if (state !== 'PICKING' || !pickActive) return; if (own(e.target)) return; if (!hoveredElement || !pickable(hoveredElement)) return; @@ -992,19 +1003,21 @@ if (state === 'PICKING') { hideHighlight(); state = 'IDLE'; return; } } - if (state === 'PICKING' && hoveredElement) { + // Arrow/Enter nav works in PICKING (hover) and CONFIGURING (selected, input empty) + var navEl = (state === 'PICKING') ? hoveredElement : (state === 'CONFIGURING') ? selectedElement : null; + if (navEl && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || (e.key === 'Enter' && state === 'PICKING'))) { let next = null; if (e.key === 'ArrowDown' && !e.shiftKey) { - next = hoveredElement.nextElementSibling; + next = navEl.nextElementSibling; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'ArrowUp' && !e.shiftKey) { - next = hoveredElement.previousElementSibling; + next = navEl.previousElementSibling; while (next && !pickable(next)) next = next.previousElementSibling; } else if (e.key === 'ArrowUp' && e.shiftKey) { - next = hoveredElement.parentElement; + next = navEl.parentElement; if (next && !pickable(next)) next = null; } else if (e.key === 'ArrowDown' && e.shiftKey) { - next = hoveredElement.firstElementChild; + next = navEl.firstElementChild; while (next && !pickable(next)) next = next.nextElementSibling; } else if (e.key === 'Enter') { e.preventDefault(); @@ -1017,7 +1030,14 @@ } if (next) { e.preventDefault(); - hoveredElement = next; + if (state === 'PICKING') { + hoveredElement = next; + } else { + // CONFIGURING: re-select the new element and refresh the bar + selectedElement = next; + showBar('configure'); + startScrollTracking(); + } showHighlight(next); next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); }