fix(webchat): slash menu clipped by overflow and missing keyboard scroll
The slash command menu renders with position: absolute; bottom: 100%
inside .agent-chat__input which has overflow: hidden, causing the
popup to be completely invisible. Introduce .agent-chat__input-wrap
as a positioning parent so the menu escapes the clipped container.
Also add scrollIntoView({ block: nearest }) after ArrowUp/ArrowDown
navigation so the active item auto-scrolls into view, scoped to the
component .agent-chat__input-wrap to avoid cross-panel matches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c86de678f3
commit
8c679db1dc
@ -363,11 +363,17 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.agent-chat__input-wrap {
|
||||
position: relative;
|
||||
margin: 0 18px 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agent-chat__input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 18px 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
|
||||
@ -513,7 +513,7 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.agent-chat__input {
|
||||
.agent-chat__input-wrap {
|
||||
margin: 0 8px 10px;
|
||||
}
|
||||
|
||||
|
||||
@ -952,6 +952,16 @@ export function renderChat(props: ChatProps) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
/** After arrow-key navigation, scroll the active slash-menu item into view. */
|
||||
const scrollSlashMenuActiveIntoView = () => {
|
||||
requestAnimationFrame(() => {
|
||||
const active = document.querySelector(".agent-chat__input-wrap .slash-menu-item--active");
|
||||
if (active) {
|
||||
active.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Slash menu navigation — arg mode
|
||||
if (vs.slashMenuOpen && vs.slashMenuMode === "args" && vs.slashMenuArgItems.length > 0) {
|
||||
@ -961,11 +971,13 @@ export function renderChat(props: ChatProps) {
|
||||
e.preventDefault();
|
||||
vs.slashMenuIndex = (vs.slashMenuIndex + 1) % len;
|
||||
requestUpdate();
|
||||
scrollSlashMenuActiveIntoView();
|
||||
return;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
vs.slashMenuIndex = (vs.slashMenuIndex - 1 + len) % len;
|
||||
requestUpdate();
|
||||
scrollSlashMenuActiveIntoView();
|
||||
return;
|
||||
case "Tab":
|
||||
e.preventDefault();
|
||||
@ -992,11 +1004,13 @@ export function renderChat(props: ChatProps) {
|
||||
e.preventDefault();
|
||||
vs.slashMenuIndex = (vs.slashMenuIndex + 1) % len;
|
||||
requestUpdate();
|
||||
scrollSlashMenuActiveIntoView();
|
||||
return;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
vs.slashMenuIndex = (vs.slashMenuIndex - 1 + len) % len;
|
||||
requestUpdate();
|
||||
scrollSlashMenuActiveIntoView();
|
||||
return;
|
||||
case "Tab":
|
||||
e.preventDefault();
|
||||
@ -1182,34 +1196,35 @@ export function renderChat(props: ChatProps) {
|
||||
}
|
||||
|
||||
<!-- Input bar -->
|
||||
<div class="agent-chat__input">
|
||||
<div class="agent-chat__input-wrap">
|
||||
${renderSlashMenu(requestUpdate, props)}
|
||||
${renderAttachmentPreview(props)}
|
||||
<div class="agent-chat__input">
|
||||
${renderAttachmentPreview(props)}
|
||||
|
||||
<input
|
||||
type="file"
|
||||
accept=${CHAT_ATTACHMENT_ACCEPT}
|
||||
multiple
|
||||
class="agent-chat__file-input"
|
||||
@change=${(e: Event) => handleFileSelect(e, props)}
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
accept=${CHAT_ATTACHMENT_ACCEPT}
|
||||
multiple
|
||||
class="agent-chat__file-input"
|
||||
@change=${(e: Event) => handleFileSelect(e, props)}
|
||||
/>
|
||||
|
||||
${vs.sttRecording && vs.sttInterimText ? html`<div class="agent-chat__stt-interim">${vs.sttInterimText}</div>` : nothing}
|
||||
${vs.sttRecording && vs.sttInterimText ? html`<div class="agent-chat__stt-interim">${vs.sttInterimText}</div>` : nothing}
|
||||
|
||||
<textarea
|
||||
${ref((el) => el && adjustTextareaHeight(el as HTMLTextAreaElement))}
|
||||
.value=${props.draft}
|
||||
dir=${detectTextDirection(props.draft)}
|
||||
?disabled=${!props.connected}
|
||||
@keydown=${handleKeyDown}
|
||||
@input=${handleInput}
|
||||
@paste=${(e: ClipboardEvent) => handlePaste(e, props)}
|
||||
placeholder=${vs.sttRecording ? "Listening..." : placeholder}
|
||||
rows="1"
|
||||
></textarea>
|
||||
<textarea
|
||||
${ref((el) => el && adjustTextareaHeight(el as HTMLTextAreaElement))}
|
||||
.value=${props.draft}
|
||||
dir=${detectTextDirection(props.draft)}
|
||||
?disabled=${!props.connected}
|
||||
@keydown=${handleKeyDown}
|
||||
@input=${handleInput}
|
||||
@paste=${(e: ClipboardEvent) => handlePaste(e, props)}
|
||||
placeholder=${vs.sttRecording ? "Listening..." : placeholder}
|
||||
rows="1"
|
||||
></textarea>
|
||||
|
||||
<div class="agent-chat__toolbar">
|
||||
<div class="agent-chat__toolbar-left">
|
||||
<div class="agent-chat__toolbar">
|
||||
<div class="agent-chat__toolbar-left">
|
||||
<button
|
||||
class="agent-chat__input-btn"
|
||||
@click=${() => {
|
||||
@ -1324,7 +1339,8 @@ export function renderChat(props: ChatProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user