Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

123 lines
3.7 KiB
TypeScript

import {
useCallback,
useLayoutEffect,
useMemo,
useState,
type CSSProperties,
type RefObject
} from "react";
import {
MENTION_PALETTE_GAP_PX,
MENTION_PALETTE_MAX_HEIGHT_PX,
MENTION_PALETTE_MIN_HEIGHT_PX,
MENTION_PALETTE_VIEWPORT_PADDING_PX,
composerPaletteZIndex,
resolveMentionPaletteZIndex,
type MentionPaletteFrame
} from "./AgentComposerChrome";
import { resolveComposerPortalTarget } from "./composerPortalTarget";
export function useMentionPaletteFrame(
inputShellRef: RefObject<HTMLDivElement | null>,
showFileMentionPalette: boolean,
viewportTopInset = MENTION_PALETTE_VIEWPORT_PADDING_PX
) {
const [mentionPaletteFrame, setMentionPaletteFrame] =
useState<MentionPaletteFrame | null>(null);
const syncMentionPaletteFrame = useCallback((): void => {
const anchor = inputShellRef.current;
if (!anchor || typeof window === "undefined") {
setMentionPaletteFrame(null);
return;
}
const rect = anchor.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const width = Math.max(
0,
Math.min(
rect.width,
viewportWidth - MENTION_PALETTE_VIEWPORT_PADDING_PX * 2
)
);
const left = Math.max(
MENTION_PALETTE_VIEWPORT_PADDING_PX,
Math.min(
rect.left,
viewportWidth - MENTION_PALETTE_VIEWPORT_PADDING_PX - width
)
);
const safeTop = Math.max(
MENTION_PALETTE_VIEWPORT_PADDING_PX,
viewportTopInset
);
const availableAbove = rect.top - MENTION_PALETTE_GAP_PX - safeTop;
const height =
availableAbove >= MENTION_PALETTE_MIN_HEIGHT_PX
? Math.min(MENTION_PALETTE_MAX_HEIGHT_PX, availableAbove)
: MENTION_PALETTE_MIN_HEIGHT_PX;
setMentionPaletteFrame({
height,
left,
portalTarget: resolveComposerPortalTarget(anchor),
top: Math.max(
safeTop,
Math.min(
rect.top - MENTION_PALETTE_GAP_PX - height,
viewportHeight - MENTION_PALETTE_VIEWPORT_PADDING_PX - height
)
),
width,
zIndex: resolveMentionPaletteZIndex(anchor)
});
}, [viewportTopInset]);
useLayoutEffect(() => {
if (!showFileMentionPalette) {
setMentionPaletteFrame(null);
return;
}
syncMentionPaletteFrame();
const anchor = inputShellRef.current;
const resizeObserver =
typeof ResizeObserver === "undefined"
? null
: new ResizeObserver(syncMentionPaletteFrame);
if (anchor) {
resizeObserver?.observe(anchor);
}
window.addEventListener("resize", syncMentionPaletteFrame);
window.addEventListener("scroll", syncMentionPaletteFrame, {
capture: true,
passive: true
});
return () => {
resizeObserver?.disconnect();
window.removeEventListener("resize", syncMentionPaletteFrame);
window.removeEventListener("scroll", syncMentionPaletteFrame, true);
};
}, [showFileMentionPalette, syncMentionPaletteFrame]);
const mentionPaletteStyle = useMemo<CSSProperties>(
() => ({
position: "fixed",
left: `${mentionPaletteFrame?.left ?? 0}px`,
top: `${mentionPaletteFrame?.top ?? 0}px`,
width: `${mentionPaletteFrame?.width ?? 0}px`,
maxWidth: `${mentionPaletteFrame?.width ?? 0}px`,
minHeight: `${MENTION_PALETTE_MIN_HEIGHT_PX}px`,
maxHeight: `${MENTION_PALETTE_MAX_HEIGHT_PX}px`,
height: `${mentionPaletteFrame?.height ?? MENTION_PALETTE_MIN_HEIGHT_PX}px`,
zIndex: composerPaletteZIndex
}),
[mentionPaletteFrame]
);
const mentionPaletteHeightPx =
mentionPaletteFrame?.height ?? MENTION_PALETTE_MIN_HEIGHT_PX;
return { mentionPaletteFrame, mentionPaletteHeightPx, mentionPaletteStyle };
}