diff --git a/packages/views/editor/extensions/mention-suggestion.tsx b/packages/views/editor/extensions/mention-suggestion.tsx index ea0485724b..0edcfa45fa 100644 --- a/packages/views/editor/extensions/mention-suggestion.tsx +++ b/packages/views/editor/extensions/mention-suggestion.tsx @@ -352,9 +352,15 @@ export const MentionList = forwardRef(
{groups.map((group) => ( diff --git a/packages/views/editor/extensions/slash-command-suggestion.tsx b/packages/views/editor/extensions/slash-command-suggestion.tsx index ff8aa958f9..6d13ed993d 100644 --- a/packages/views/editor/extensions/slash-command-suggestion.tsx +++ b/packages/views/editor/extensions/slash-command-suggestion.tsx @@ -127,7 +127,11 @@ export const SlashCommandList = forwardRef< : item.description; return ( -
+ // Height budget clamps to min(design max, viewport-aware + // `--suggestion-available-height` from suggestion-popup.tsx's size + // middleware), falling back to the design max when rendered standalone. + // Single height authority — mirrors MentionList. +
{items.map((item, index) => { const description = describe(item); return ( diff --git a/packages/views/editor/extensions/suggestion-popup.tsx b/packages/views/editor/extensions/suggestion-popup.tsx index 834af16be4..0559e037eb 100644 --- a/packages/views/editor/extensions/suggestion-popup.tsx +++ b/packages/views/editor/extensions/suggestion-popup.tsx @@ -126,7 +126,17 @@ export function createSuggestionPopupRender< getBoundingClientRect: () => clientRect() ?? new DOMRect(), }; computePosition(virtualEl, el, { - placement: "bottom-start", + // Open upward by default. The dominant hosts are bottom-anchored + // composers (chat input, issue comment/reply) whose roomy side is above + // the caret; preferring that side means `size` clamps to a large budget + // with no visible compression, and `flip` only sends it down in the rare + // case the caret sits near the viewport top. A `bottom-start` default + // instead stayed down whenever *any* space existed below — even when far + // more room was above — which read as "mostly opens down and gets + // squashed". Document-body editors (issue/project description, agent + // instructions) also host this popup; there the caret usually has room + // both ways, so `flip` keeps them on-screen regardless of the default. + placement: "top-start", strategy: "fixed", middleware: [ offset(6), @@ -135,7 +145,20 @@ export function createSuggestionPopupRender< size({ padding: 8, apply({ availableHeight }) { - el.style.maxHeight = `${Math.max(120, availableHeight)}px`; + // Publish the viewport-aware height budget as a CSS variable so + // the list component (the real scroll container) can clamp its own + // max-height to `min(designMax, availableHeight)`. Writing + // maxHeight on this wrapper is inert — the wrapper does not clip, + // the inner list scrolls — so it would let a fixed-height list + // overflow past the viewport edge. No floor here: when space is + // tight a short scrollable list beats one clipped off-screen. + el.style.setProperty( + "--suggestion-available-height", + // Guard against a negative budget (caret scrolled just out of + // the boundary): a negative max-height is invalid CSS and would + // drop the clamp entirely, letting the list overflow. + `${Math.max(0, Math.round(availableHeight))}px`, + ); }, }), ],