cogsbox-state 0.5.340 → 0.5.343
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CogsState.jsx +567 -557
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +61 -78
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1804,7 +1804,7 @@ function createProxyHandler<T>(
|
|
|
1804
1804
|
): VirtualStateObjectResult<any[]> => {
|
|
1805
1805
|
const {
|
|
1806
1806
|
itemHeight = 50,
|
|
1807
|
-
overscan =
|
|
1807
|
+
overscan = 6, // Keeping your working overscan value
|
|
1808
1808
|
stickToBottom = false,
|
|
1809
1809
|
} = options;
|
|
1810
1810
|
|
|
@@ -1813,11 +1813,11 @@ function createProxyHandler<T>(
|
|
|
1813
1813
|
startIndex: 0,
|
|
1814
1814
|
endIndex: 10,
|
|
1815
1815
|
});
|
|
1816
|
-
|
|
1817
|
-
// This ref tracks if the user is locked to the bottom.
|
|
1816
|
+
// The MASTER SWITCH for auto-scrolling. Starts true.
|
|
1818
1817
|
const isLockedToBottomRef = useRef(stickToBottom);
|
|
1818
|
+
// Store the previous item count to detect when new items are added.
|
|
1819
|
+
const prevTotalCountRef = useRef(0);
|
|
1819
1820
|
|
|
1820
|
-
// This state triggers a re-render when item heights change.
|
|
1821
1821
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1822
1822
|
|
|
1823
1823
|
useEffect(() => {
|
|
@@ -1835,7 +1835,6 @@ function createProxyHandler<T>(
|
|
|
1835
1835
|
) as any[];
|
|
1836
1836
|
const totalCount = sourceArray.length;
|
|
1837
1837
|
|
|
1838
|
-
// Calculate heights from shadow state. This runs when data or measurements change.
|
|
1839
1838
|
const { totalHeight, positions } = useMemo(() => {
|
|
1840
1839
|
const shadowArray =
|
|
1841
1840
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
@@ -1857,7 +1856,6 @@ function createProxyHandler<T>(
|
|
|
1857
1856
|
shadowUpdateTrigger,
|
|
1858
1857
|
]);
|
|
1859
1858
|
|
|
1860
|
-
// Memoize the virtualized slice of data.
|
|
1861
1859
|
const virtualState = useMemo(() => {
|
|
1862
1860
|
const start = Math.max(0, range.startIndex);
|
|
1863
1861
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1871,48 +1869,18 @@ function createProxyHandler<T>(
|
|
|
1871
1869
|
validIndices,
|
|
1872
1870
|
});
|
|
1873
1871
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1874
|
-
|
|
1875
|
-
//
|
|
1876
|
-
// // When count increases, immediately adjust range to show bottom
|
|
1877
|
-
// const container = containerRef.current;
|
|
1878
|
-
// const visibleCount = Math.ceil(
|
|
1879
|
-
// container.clientHeight / itemHeight
|
|
1880
|
-
// );
|
|
1881
|
-
|
|
1882
|
-
// // Set range to show the last items including the new one
|
|
1883
|
-
// setRange({
|
|
1884
|
-
// startIndex: Math.max(
|
|
1885
|
-
// 0,
|
|
1886
|
-
// totalCount - visibleCount - overscan
|
|
1887
|
-
// ),
|
|
1888
|
-
// endIndex: totalCount,
|
|
1889
|
-
// });
|
|
1890
|
-
|
|
1891
|
-
// // Then scroll to bottom after a short delay
|
|
1892
|
-
// setTimeout(() => {
|
|
1893
|
-
// container.scrollTop = container.scrollHeight + 9999;
|
|
1894
|
-
// }, 200);
|
|
1895
|
-
// }
|
|
1896
|
-
// }, [totalCount]);
|
|
1897
|
-
// This is the main effect that handles all scrolling and updates.
|
|
1898
|
-
// This is the main effect that handles all scrolling and updates.
|
|
1872
|
+
|
|
1873
|
+
// This layout effect is for SCROLLING and RANGE updates ONLY
|
|
1899
1874
|
useLayoutEffect(() => {
|
|
1900
1875
|
const container = containerRef.current;
|
|
1901
1876
|
if (!container) return;
|
|
1902
1877
|
|
|
1903
|
-
|
|
1904
|
-
// The check is made more forgiving (< itemHeight) to handle measurement delays.
|
|
1905
|
-
const wasScrolledToBottom =
|
|
1906
|
-
container.scrollHeight -
|
|
1907
|
-
container.scrollTop -
|
|
1908
|
-
container.clientHeight <
|
|
1909
|
-
itemHeight;
|
|
1878
|
+
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1910
1879
|
|
|
1911
1880
|
// This function determines what's visible in the viewport.
|
|
1912
1881
|
const updateVirtualRange = () => {
|
|
1913
1882
|
if (!container) return;
|
|
1914
1883
|
const { scrollTop, clientHeight } = container;
|
|
1915
|
-
// ... (rest of the function is the same, no changes needed)
|
|
1916
1884
|
let low = 0,
|
|
1917
1885
|
high = totalCount - 1;
|
|
1918
1886
|
while (low <= high) {
|
|
@@ -1933,61 +1901,75 @@ function createProxyHandler<T>(
|
|
|
1933
1901
|
setRange({ startIndex, endIndex });
|
|
1934
1902
|
};
|
|
1935
1903
|
|
|
1936
|
-
//
|
|
1937
|
-
const handleUserScroll = () => {
|
|
1938
|
-
// When the user scrolls, update the ref so we know their intent for the *next* update.
|
|
1939
|
-
isLockedToBottomRef.current =
|
|
1940
|
-
container.scrollHeight -
|
|
1941
|
-
container.scrollTop -
|
|
1942
|
-
container.clientHeight <
|
|
1943
|
-
itemHeight; // Strict check for user action
|
|
1944
|
-
// Then, just render what's visible at the new position.
|
|
1945
|
-
updateVirtualRange();
|
|
1946
|
-
};
|
|
1947
|
-
|
|
1948
|
-
// Add the listener for user scrolling.
|
|
1949
|
-
container.addEventListener("scroll", handleUserScroll, {
|
|
1950
|
-
passive: true,
|
|
1951
|
-
});
|
|
1952
|
-
|
|
1953
|
-
let scrollTimeoutId: NodeJS.Timeout | undefined;
|
|
1954
|
-
|
|
1955
|
-
// --- STEP 2: Conditionally schedule the SMOOTH scroll ---
|
|
1904
|
+
// If new items were added AND we are locked to the bottom, scroll smoothly.
|
|
1956
1905
|
if (
|
|
1957
1906
|
stickToBottom &&
|
|
1958
|
-
|
|
1907
|
+
hasNewItems &&
|
|
1908
|
+
isLockedToBottomRef.current
|
|
1959
1909
|
) {
|
|
1960
|
-
// A timeout is
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1910
|
+
// A timeout is essential for smooth scrolling to work after a render.
|
|
1911
|
+
const scrollTimeout = setTimeout(() => {
|
|
1912
|
+
if (container) {
|
|
1913
|
+
container.scrollTo({
|
|
1914
|
+
top: container.scrollHeight,
|
|
1915
|
+
behavior: "smooth",
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
}, 50); // A small buffer (50ms) is more robust for measurement to catch up.
|
|
1919
|
+
|
|
1920
|
+
// Cleanup the timeout if the component unmounts or effect re-runs.
|
|
1921
|
+
return () => clearTimeout(scrollTimeout);
|
|
1968
1922
|
}
|
|
1969
1923
|
|
|
1970
|
-
// Always
|
|
1924
|
+
// Always update the visible range.
|
|
1971
1925
|
updateVirtualRange();
|
|
1972
|
-
|
|
1973
|
-
// Cleanup function is vital to prevent memory leaks.
|
|
1974
|
-
return () => {
|
|
1975
|
-
container.removeEventListener("scroll", handleUserScroll);
|
|
1976
|
-
if (scrollTimeoutId) {
|
|
1977
|
-
clearTimeout(scrollTimeoutId);
|
|
1978
|
-
}
|
|
1979
|
-
};
|
|
1980
1926
|
}, [
|
|
1981
1927
|
totalCount,
|
|
1982
1928
|
positions,
|
|
1983
1929
|
totalHeight,
|
|
1984
1930
|
stickToBottom,
|
|
1985
1931
|
itemHeight,
|
|
1986
|
-
]); //
|
|
1932
|
+
]); // Dependencies for scrolling/range.
|
|
1933
|
+
|
|
1934
|
+
// This effect is ONLY for handling USER SCROLLS and the initial scroll.
|
|
1935
|
+
useEffect(() => {
|
|
1936
|
+
const container = containerRef.current;
|
|
1937
|
+
if (!container) return;
|
|
1938
|
+
|
|
1939
|
+
// On initial mount, if we need to stick to the bottom, do it instantly.
|
|
1940
|
+
if (isLockedToBottomRef.current) {
|
|
1941
|
+
container.scrollTop = container.scrollHeight;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
const handleUserScroll = () => {
|
|
1945
|
+
if (!container) return;
|
|
1946
|
+
// If the user scrolls up, break the lock.
|
|
1947
|
+
const isAtBottom =
|
|
1948
|
+
container.scrollHeight -
|
|
1949
|
+
container.scrollTop -
|
|
1950
|
+
container.clientHeight <
|
|
1951
|
+
1;
|
|
1952
|
+
if (!isAtBottom) {
|
|
1953
|
+
isLockedToBottomRef.current = false;
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
|
|
1957
|
+
container.addEventListener("scroll", handleUserScroll, {
|
|
1958
|
+
passive: true,
|
|
1959
|
+
});
|
|
1960
|
+
return () =>
|
|
1961
|
+
container.removeEventListener("scroll", handleUserScroll);
|
|
1962
|
+
}, []); // This runs only once on mount.
|
|
1963
|
+
|
|
1964
|
+
// After every render, update the previous count ref.
|
|
1965
|
+
useEffect(() => {
|
|
1966
|
+
prevTotalCountRef.current = totalCount;
|
|
1967
|
+
});
|
|
1987
1968
|
|
|
1988
1969
|
const scrollToBottom = useCallback(
|
|
1989
1970
|
(behavior: ScrollBehavior = "smooth") => {
|
|
1990
1971
|
if (containerRef.current) {
|
|
1972
|
+
// Re-enable the lock when the user explicitly asks to scroll to bottom.
|
|
1991
1973
|
isLockedToBottomRef.current = true;
|
|
1992
1974
|
containerRef.current.scrollTo({
|
|
1993
1975
|
top: containerRef.current.scrollHeight,
|
|
@@ -2001,6 +1983,7 @@ function createProxyHandler<T>(
|
|
|
2001
1983
|
const scrollToIndex = useCallback(
|
|
2002
1984
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2003
1985
|
if (containerRef.current && positions[index] !== undefined) {
|
|
1986
|
+
// Scrolling to a specific index should break the lock.
|
|
2004
1987
|
isLockedToBottomRef.current = false;
|
|
2005
1988
|
containerRef.current.scrollTo({
|
|
2006
1989
|
top: positions[index],
|