Fix autocomplete popover flicker
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState, type ReactElement } from "react";
|
||||
import { Keyboard, Platform, StatusBar, View, type LayoutChangeEvent } from "react-native";
|
||||
import { useEffect, useMemo, useState, type ReactElement } from "react";
|
||||
import { Keyboard, Platform, StatusBar, View, useWindowDimensions } from "react-native";
|
||||
import { Portal } from "@gorhom/portal";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
@@ -54,7 +54,7 @@ export function AutocompletePopover({
|
||||
emptyText,
|
||||
}: AutocompletePopoverProps): ReactElement | null {
|
||||
const [anchorRect, setAnchorRect] = useState<Rect | null>(null);
|
||||
const [contentSize, setContentSize] = useState<{ width: number; height: number } | null>(null);
|
||||
const { height: windowHeight } = useWindowDimensions();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const { height: rawKeyboardHeight } = useReanimatedKeyboardAnimation();
|
||||
@@ -76,7 +76,6 @@ export function AutocompletePopover({
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
setAnchorRect(null);
|
||||
setContentSize(null);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
@@ -106,34 +105,15 @@ export function AutocompletePopover({
|
||||
};
|
||||
}, [visible, anchorRef, openShift, shift]);
|
||||
|
||||
const handleLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
setContentSize({ width, height });
|
||||
}, []);
|
||||
|
||||
const baseStyle = useMemo(() => {
|
||||
if (!anchorRect) return null;
|
||||
if (!contentSize) {
|
||||
// Have the anchor, waiting on the popover's own height. Render with the
|
||||
// final width so the inner Autocomplete lays out at its final size, but
|
||||
// stay invisible — the first visible paint will already be at the correct
|
||||
// top. Mirrors combobox.tsx `shouldHideDesktopContent`. See
|
||||
// docs/floating-panels.md "the two-measurement flash".
|
||||
return inlineUnistylesStyle({
|
||||
position: "absolute" as const,
|
||||
top: 0,
|
||||
left: anchorRect.x,
|
||||
width: anchorRect.width,
|
||||
opacity: 0,
|
||||
});
|
||||
}
|
||||
return inlineUnistylesStyle({
|
||||
position: "absolute" as const,
|
||||
top: anchorRect.y - contentSize.height - OFFSET_FROM_ANCHOR,
|
||||
bottom: windowHeight - anchorRect.y + OFFSET_FROM_ANCHOR,
|
||||
left: anchorRect.x,
|
||||
width: anchorRect.width,
|
||||
});
|
||||
}, [anchorRect, contentSize]);
|
||||
}, [anchorRect, windowHeight]);
|
||||
|
||||
const animatedTransformStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ translateY: openShift.value - shift.value }],
|
||||
@@ -149,7 +129,7 @@ export function AutocompletePopover({
|
||||
return (
|
||||
<Portal>
|
||||
<View style={styles.overlay} pointerEvents="box-none">
|
||||
<Animated.View style={composedStyle} onLayout={handleLayout}>
|
||||
<Animated.View testID="composer-autocomplete-popover" style={composedStyle}>
|
||||
<Autocomplete
|
||||
options={options}
|
||||
selectedIndex={selectedIndex}
|
||||
|
||||
@@ -206,6 +206,7 @@ export function Autocomplete({
|
||||
[ensureActiveItemVisible],
|
||||
);
|
||||
|
||||
const selectedOption = options[selectedIndex];
|
||||
const containerStyle = useMemo(() => [styles.container, { maxHeight }], [maxHeight]);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -238,8 +239,6 @@ export function Autocomplete({
|
||||
);
|
||||
}
|
||||
|
||||
const selectedOption = options[selectedIndex];
|
||||
|
||||
return (
|
||||
<View style={styles.outerWrapper}>
|
||||
{selectedOption?.kind === "command" && selectedOption.description ? (
|
||||
|
||||
@@ -168,7 +168,7 @@ function resolveAutocompleteIsLoading(args: {
|
||||
optionsLength: number;
|
||||
}): boolean {
|
||||
if (args.mode === "command") {
|
||||
return args.isCommandsLoading;
|
||||
return args.isCommandsLoading && args.optionsLength === 0;
|
||||
}
|
||||
if (args.mode === "file") {
|
||||
return (
|
||||
@@ -251,7 +251,7 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
|
||||
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||
|
||||
const mode = resolveAutocompleteMode({ showFileAutocomplete, showCommandAutocomplete });
|
||||
const isVisible = resolveAutocompleteIsVisible({
|
||||
const canShowAutocomplete = resolveAutocompleteIsVisible({
|
||||
mode,
|
||||
canLoadCommands,
|
||||
serverId,
|
||||
@@ -270,6 +270,8 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
|
||||
draftConfig: queryDraftConfig,
|
||||
});
|
||||
|
||||
const isVisible = canShowAutocomplete && !(mode === "command" && isCommandsLoading);
|
||||
|
||||
const fileSuggestionsQuery = useQuery({
|
||||
queryKey: [
|
||||
"directorySuggestions",
|
||||
|
||||
Reference in New Issue
Block a user