@lifi/widget
Version:
LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.
76 lines • 3.87 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { useVirtualizer } from '@tanstack/react-virtual';
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import { useChainOrderStore } from '../../stores/chains/ChainOrderStore';
import { List } from './ChainList.style';
import { ChainListItem } from './ChainListItem';
export const VirtualizedChainList = ({ chains, onSelect, selectedChainId, itemsSize, scrollElementRef, withPinnedChains, }) => {
const selectedChainIdRef = useRef(selectedChainId); // Store the initial selected chain ID to scroll to it once chains are loaded
const hasScrolledRef = useRef(false);
const [pinnedChains, setPinnedChain] = useChainOrderStore((state) => [
state.pinnedChains,
state.setPinnedChain,
]);
const onPin = useCallback((chainId) => {
setPinnedChain(chainId);
}, [setPinnedChain]);
const sortedChains = useMemo(() => {
if (!pinnedChains.length) {
return chains;
}
// Pinning logic: move pinned chains to the top of the list
const pinned = pinnedChains
.map((id) => chains.find((c) => c.id === id))
.filter(Boolean);
const pinnedIds = new Set(pinned.map((c) => c.id));
const rest = chains.filter((c) => !pinnedIds.has(c.id));
return [...pinned, ...rest];
}, [chains, pinnedChains]);
const getItemKey = useCallback((index) => {
return `${sortedChains[index].id}-${index}`;
}, [sortedChains]);
const { getVirtualItems, getTotalSize, measure, scrollToIndex, range } = useVirtualizer({
count: sortedChains.length,
overscan: 3,
paddingEnd: 0,
getScrollElement: () => scrollElementRef.current,
estimateSize: () => {
return itemsSize === 'small' ? 48 : 60;
},
getItemKey,
});
// Using mountOnEnter of the ExpansionTransition component
// leads to a short delay for setting up scrollElementRef,
// which in turn leads to getVirtualItems() returning an empty array.
// Workaround: Re-measure when scroll element becomes available
useEffect(() => {
if (scrollElementRef.current) {
measure();
}
}, [measure, scrollElementRef.current]);
useLayoutEffect(() => {
// Only scroll if sortedChains is not empty and we haven't scrolled yet
if (!hasScrolledRef.current && sortedChains.length > 0 && range) {
const selectedChainIndex = sortedChains.findIndex((chain) => chain.id === selectedChainIdRef.current);
if (selectedChainIndex !== -1) {
// Only scroll if the selected chain is not in the visible range
// +1 and -1 to account for partially visible items
if (range.startIndex + 1 > selectedChainIndex ||
range.endIndex - 1 < selectedChainIndex) {
requestAnimationFrame(() => {
scrollToIndex(selectedChainIndex, {
align: 'center',
behavior: 'smooth',
});
});
}
}
hasScrolledRef.current = true; // Mark as scrolled (when needed)
}
}, [sortedChains, scrollToIndex, range]);
return (_jsx(List, { className: "long-list", style: { height: getTotalSize() }, disablePadding: true, children: getVirtualItems().map((item) => {
const chain = sortedChains[item.index];
return (_jsx(ChainListItem, { chain: chain, onSelect: onSelect, selected: chain.id === selectedChainId, itemsSize: itemsSize, size: item.size, start: item.start, withPin: withPinnedChains, isPinned: pinnedChains.includes(chain.id), onPin: onPin }, item.key));
}) }));
};
//# sourceMappingURL=VirtualizedChainList.js.map