fix: profile bio
This commit is contained in:
@@ -69,6 +69,7 @@ export const MentionTextarea = forwardRef<
|
||||
const [pos, setPos] = useState<PanelPos | null>(null);
|
||||
const reqRef = useRef(0);
|
||||
const blurTimer = useRef<number | null>(null);
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Detect the active @token from the text up to the caret.
|
||||
function detect(el: HTMLTextAreaElement) {
|
||||
@@ -78,8 +79,10 @@ export const MentionTextarea = forwardRef<
|
||||
setMention(null);
|
||||
return;
|
||||
}
|
||||
setMention({ start: caret - m[2].length - 1, query: m[2] });
|
||||
setActive(0);
|
||||
const next = { start: caret - m[2].length - 1, query: m[2] };
|
||||
|
||||
// Reposition unconditionally — the field grows as text wraps, so the panel
|
||||
// has to stay pinned to it even when the token itself hasn't changed.
|
||||
const r = el.getBoundingClientRect();
|
||||
// Prefer opening ABOVE the field: that's what the comment composer needs
|
||||
// (it sits at the bottom of a drawer, often above the on-screen keyboard).
|
||||
@@ -94,6 +97,18 @@ export const MentionTextarea = forwardRef<
|
||||
? { bottom: window.innerHeight - r.top + 6 }
|
||||
: { top: r.bottom + 6 }),
|
||||
});
|
||||
|
||||
// Bail out if the token is byte-for-byte what we already have. detect()
|
||||
// also runs on keyup and click, so re-seeding an identical mention would:
|
||||
// • reset `active` to 0 — which cancelled the ↑/↓ highlight the instant
|
||||
// the key came back up, making arrow navigation look dead; and
|
||||
// • hand the fetch effect a fresh object identity, re-requesting
|
||||
// suggestions on every single keystroke.
|
||||
if (mention && mention.start === next.start && mention.query === next.query) {
|
||||
return;
|
||||
}
|
||||
setMention(next);
|
||||
setActive(0);
|
||||
}
|
||||
|
||||
// Fetch suggestions (debounced) whenever the active token changes.
|
||||
@@ -136,6 +151,17 @@ export const MentionTextarea = forwardRef<
|
||||
|
||||
const listOpen = !!mention && (loading || items.length > 0);
|
||||
|
||||
// Keep the highlighted row visible: the panel caps at max-h-56, which holds
|
||||
// ~5 of the 8 suggestions, so arrowing past the fifth would otherwise move
|
||||
// the highlight out of sight. `block: "nearest"` scrolls the panel the
|
||||
// minimum amount and never scrolls the page behind it.
|
||||
useEffect(() => {
|
||||
if (!listOpen) return;
|
||||
listRef.current
|
||||
?.querySelector<HTMLElement>(`[data-idx="${active}"]`)
|
||||
?.scrollIntoView({ block: "nearest" });
|
||||
}, [active, listOpen]);
|
||||
|
||||
function onKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||
if (listOpen && items.length > 0) {
|
||||
if (e.key === "ArrowDown") {
|
||||
@@ -195,6 +221,7 @@ export const MentionTextarea = forwardRef<
|
||||
{listOpen && pos && typeof document !== "undefined"
|
||||
? createPortal(
|
||||
<div
|
||||
ref={listRef}
|
||||
role="listbox"
|
||||
aria-label="Mention suggestions"
|
||||
style={{
|
||||
@@ -216,6 +243,7 @@ export const MentionTextarea = forwardRef<
|
||||
key={u.id ?? u.handle}
|
||||
type="button"
|
||||
role="option"
|
||||
data-idx={i}
|
||||
aria-selected={i === active}
|
||||
// Keep textarea focus + cancel the pending blur-close so the
|
||||
// click registers before the list would otherwise close.
|
||||
|
||||
Reference in New Issue
Block a user