| ... | @@ -144,6 +144,7 @@ export function adjacentModelPickerProvider(input: { | ... | @@ -144,6 +144,7 @@ export function adjacentModelPickerProvider(input: { |
| 144 | } | 144 | } |
| 145 | | 145 | |
| 146 | const EMPTY_MODEL_JUMP_LABELS = new Map<string, string>(); | 146 | const EMPTY_MODEL_JUMP_LABELS = new Map<string, string>(); |
| | 147 | const MODEL_LIST_ESTIMATED_ITEM_SIZE = 52; |
| 147 | | 148 | |
| 148 | function ModelListSeparator() { | 149 | function ModelListSeparator() { |
| 149 | return <div className="h-0.5" />; | 150 | return <div className="h-0.5" />; |
| ... | @@ -197,6 +198,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -197,6 +198,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 197 | const [showBottomScrollFade, setShowBottomScrollFade] = useState(false); | 198 | const [showBottomScrollFade, setShowBottomScrollFade] = useState(false); |
| 198 | const searchInputRef = useRef<HTMLInputElement>(null); | 199 | const searchInputRef = useRef<HTMLInputElement>(null); |
| 199 | const modelListRef = useRef<LegendListRef | null>(null); | 200 | const modelListRef = useRef<LegendListRef | null>(null); |
| | 201 | const pickerContentRef = useRef<HTMLDivElement>(null); |
| 200 | const highlightedModelKeyRef = useRef<string | null>(null); | 202 | const highlightedModelKeyRef = useRef<string | null>(null); |
| 201 | const favorites = useClientSettings((s) => s.favorites ?? []); | 203 | const favorites = useClientSettings((s) => s.favorites ?? []); |
| 202 | const activeEntry = props.instanceEntries.find( | 204 | const activeEntry = props.instanceEntries.find( |
| ... | @@ -699,6 +701,17 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -699,6 +701,17 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 699 | ), | 701 | ), |
| 700 | [visibleModels], | 702 | [visibleModels], |
| 701 | ); | 703 | ); |
| | 704 | const [modelListContentSize, setModelListContentSize] = useState( |
| | 705 | () => filteredItemKeys.length * MODEL_LIST_ESTIMATED_ITEM_SIZE, |
| | 706 | ); |
| | 707 | const [searchHeight, setSearchHeight] = useState(0); |
| | 708 | useLayoutEffect( |
| | 709 | () => modelListRef.current?.getState().listen("totalSize", setModelListContentSize), |
| | 710 | [], |
| | 711 | ); |
| | 712 | // Fit the list to its rows plus the combobox list `py-1` and LegendList `py-1.5`. |
| | 713 | const modelListHeight = |
| | 714 | filteredItemKeys.length === 0 ? 0 : `calc(${modelListContentSize}px + var(--spacing) * 5)`; |
| 702 | const updateModelListScrollFades = useCallback(() => { | 715 | const updateModelListScrollFades = useCallback(() => { |
| 703 | const scrollElement = modelListRef.current?.getScrollableNode(); | 716 | const scrollElement = modelListRef.current?.getScrollableNode(); |
| 704 | if (!(scrollElement instanceof HTMLElement)) { | 717 | if (!(scrollElement instanceof HTMLElement)) { |
| ... | @@ -815,7 +828,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -815,7 +828,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 815 | return ( | 828 | return ( |
| 816 | <TooltipProvider delay={0}> | 829 | <TooltipProvider delay={0}> |
| 817 | <div | 830 | <div |
| 818 | className="relative flex h-screen max-h-86.5 w-screen max-w-90 flex-row overflow-hidden" | 831 | ref={pickerContentRef} |
| | 832 | className="relative flex max-h-86.5 w-screen max-w-90 flex-row overflow-hidden" |
| | 833 | // Hold the height from when the search started; results scroll instead of resizing. |
| | 834 | style={isSearching ? { height: searchHeight } : undefined} |
| 819 | data-model-picker-content="true" | 835 | data-model-picker-content="true" |
| 820 | > | 836 | > |
| 821 | {/* Sidebar */} | 837 | {/* Sidebar */} |
| ... | @@ -890,7 +906,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -890,7 +906,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 890 | ref={searchInputRef} | 906 | ref={searchInputRef} |
| 891 | placeholder="Search models..." | 907 | placeholder="Search models..." |
| 892 | value={searchQuery} | 908 | value={searchQuery} |
| 893 | onChange={(e) => setSearchQuery(e.target.value)} | 909 | onChange={(e) => { |
| | 910 | if (!isSearching) setSearchHeight(pickerContentRef.current?.offsetHeight ?? 0); |
| | 911 | setSearchQuery(e.target.value); |
| | 912 | }} |
| 894 | onKeyDown={(e) => { | 913 | onKeyDown={(e) => { |
| 895 | if ( | 914 | if ( |
| 896 | showSidebar && | 915 | showSidebar && |
| ... | @@ -944,7 +963,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -944,7 +963,10 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 944 | /> | 963 | /> |
| 945 | | 964 | |
| 946 | {/* Model list */} | 965 | {/* Model list */} |
| 947 | <div className="relative min-h-0 flex-1 overflow-hidden pr-px"> | 966 | <div |
| | 967 | className="relative min-h-0 overflow-hidden pr-px" |
| | 968 | style={{ height: modelListHeight }} |
| | 969 | > |
| 948 | <ComboboxListVirtualized> | 970 | <ComboboxListVirtualized> |
| 949 | <LegendList<string> | 971 | <LegendList<string> |
| 950 | ref={modelListRef} | 972 | ref={modelListRef} |
| ... | @@ -1013,7 +1035,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -1013,7 +1035,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 1013 | /> | 1035 | /> |
| 1014 | ); | 1036 | ); |
| 1015 | }} | 1037 | }} |
| 1016 | estimatedItemSize={52} | 1038 | estimatedItemSize={MODEL_LIST_ESTIMATED_ITEM_SIZE} |
| 1017 | drawDistance={480} | 1039 | drawDistance={480} |
| 1018 | recycleItems | 1040 | recycleItems |
| 1019 | contentContainerClassName="pl-2 pr-px" | 1041 | contentContainerClassName="pl-2 pr-px" |