cogsbox-state 0.5.335 → 0.5.336
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 -563
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +52 -30
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1814,9 +1814,11 @@ function createProxyHandler<T>(
|
|
|
1814
1814
|
endIndex: 10,
|
|
1815
1815
|
});
|
|
1816
1816
|
|
|
1817
|
-
|
|
1817
|
+
// This ref tracks if the user is locked to the bottom.
|
|
1818
|
+
const isLockedToBottomRef = useRef(stickToBottom);
|
|
1819
|
+
|
|
1820
|
+
// This state triggers a re-render when item heights change.
|
|
1818
1821
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1819
|
-
const lastTotalCountRef = useRef(0); // Track previous count
|
|
1820
1822
|
|
|
1821
1823
|
useEffect(() => {
|
|
1822
1824
|
const unsubscribe = getGlobalStore
|
|
@@ -1833,6 +1835,7 @@ function createProxyHandler<T>(
|
|
|
1833
1835
|
) as any[];
|
|
1834
1836
|
const totalCount = sourceArray.length;
|
|
1835
1837
|
|
|
1838
|
+
// Calculate heights from shadow state. This runs when data or measurements change.
|
|
1836
1839
|
const { totalHeight, positions } = useMemo(() => {
|
|
1837
1840
|
const shadowArray =
|
|
1838
1841
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
@@ -1854,6 +1857,7 @@ function createProxyHandler<T>(
|
|
|
1854
1857
|
shadowUpdateTrigger,
|
|
1855
1858
|
]);
|
|
1856
1859
|
|
|
1860
|
+
// Memoize the virtualized slice of data.
|
|
1857
1861
|
const virtualState = useMemo(() => {
|
|
1858
1862
|
const start = Math.max(0, range.startIndex);
|
|
1859
1863
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1867,14 +1871,40 @@ function createProxyHandler<T>(
|
|
|
1867
1871
|
validIndices,
|
|
1868
1872
|
});
|
|
1869
1873
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1874
|
+
useEffect(() => {
|
|
1875
|
+
if (stickToBottom && totalCount > 0 && containerRef.current) {
|
|
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
|
+
);
|
|
1870
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;
|
|
1894
|
+
}, 100);
|
|
1895
|
+
}
|
|
1896
|
+
}, [totalCount]);
|
|
1897
|
+
// This is the main effect that handles all scrolling and updates.
|
|
1871
1898
|
useLayoutEffect(() => {
|
|
1872
1899
|
const container = containerRef.current;
|
|
1873
1900
|
if (!container) return;
|
|
1874
1901
|
|
|
1902
|
+
// This function determines what's visible in the viewport.
|
|
1875
1903
|
const updateVirtualRange = () => {
|
|
1876
1904
|
if (!container) return;
|
|
1877
|
-
const { scrollTop } = container;
|
|
1905
|
+
const { scrollTop, clientHeight } = container;
|
|
1906
|
+
|
|
1907
|
+
// Find the first visible item
|
|
1878
1908
|
let low = 0,
|
|
1879
1909
|
high = totalCount - 1;
|
|
1880
1910
|
while (low <= high) {
|
|
@@ -1883,8 +1913,10 @@ function createProxyHandler<T>(
|
|
|
1883
1913
|
else high = mid - 1;
|
|
1884
1914
|
}
|
|
1885
1915
|
const startIndex = Math.max(0, high - overscan);
|
|
1916
|
+
|
|
1917
|
+
// Find the last visible item
|
|
1886
1918
|
let endIndex = startIndex;
|
|
1887
|
-
const visibleEnd = scrollTop +
|
|
1919
|
+
const visibleEnd = scrollTop + clientHeight;
|
|
1888
1920
|
while (
|
|
1889
1921
|
endIndex < totalCount &&
|
|
1890
1922
|
positions[endIndex]! < visibleEnd
|
|
@@ -1892,54 +1924,44 @@ function createProxyHandler<T>(
|
|
|
1892
1924
|
endIndex++;
|
|
1893
1925
|
}
|
|
1894
1926
|
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1927
|
+
|
|
1895
1928
|
setRange({ startIndex, endIndex });
|
|
1896
1929
|
};
|
|
1897
1930
|
|
|
1931
|
+
// This function handles ONLY user-initiated scrolls.
|
|
1898
1932
|
const handleUserScroll = () => {
|
|
1933
|
+
// The key: check if we are scrolled to the bottom.
|
|
1899
1934
|
isLockedToBottomRef.current =
|
|
1900
1935
|
container.scrollHeight -
|
|
1901
1936
|
container.scrollTop -
|
|
1902
1937
|
container.clientHeight <
|
|
1903
1938
|
1;
|
|
1939
|
+
|
|
1940
|
+
// After any scroll, update what's visible.
|
|
1904
1941
|
updateVirtualRange();
|
|
1905
1942
|
};
|
|
1906
1943
|
|
|
1944
|
+
// Add the listener for user scrolling.
|
|
1907
1945
|
container.addEventListener("scroll", handleUserScroll, {
|
|
1908
1946
|
passive: true,
|
|
1909
1947
|
});
|
|
1910
|
-
|
|
1911
|
-
//
|
|
1912
|
-
if
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
console.log("hasNewItems", hasNewItems);
|
|
1918
|
-
if (isInitialLoad) {
|
|
1919
|
-
// First load - always scroll to bottom
|
|
1920
|
-
setTimeout(() => {
|
|
1921
|
-
container.scrollTop = container.scrollHeight;
|
|
1922
|
-
isLockedToBottomRef.current = true;
|
|
1923
|
-
}, 1000); // Longer delay for initial load
|
|
1924
|
-
} else if (hasNewItems && isLockedToBottomRef.current) {
|
|
1925
|
-
// New items added and user is at bottom - smooth scroll
|
|
1926
|
-
setTimeout(() => {
|
|
1927
|
-
container.scrollTo({
|
|
1928
|
-
top: container.scrollHeight,
|
|
1929
|
-
behavior: "smooth",
|
|
1930
|
-
});
|
|
1931
|
-
}, 100);
|
|
1932
|
-
}
|
|
1933
|
-
// If user has scrolled up, don't auto-scroll
|
|
1948
|
+
|
|
1949
|
+
// --- THE AUTO-SCROLL LOGIC ---
|
|
1950
|
+
// This checks if we should auto-scroll *after* a render has occurred.
|
|
1951
|
+
if (stickToBottom && isLockedToBottomRef.current) {
|
|
1952
|
+
// If we were at the bottom before this new item was added,
|
|
1953
|
+
// scroll to the new bottom.
|
|
1954
|
+
container.scrollTop = container.scrollHeight; // <--- THIS IS THE SCROLL LOGIC
|
|
1934
1955
|
}
|
|
1935
1956
|
|
|
1957
|
+
// Always calculate the initial visible range.
|
|
1936
1958
|
updateVirtualRange();
|
|
1937
|
-
lastTotalCountRef.current = totalCount;
|
|
1938
1959
|
|
|
1960
|
+
// Cleanup function is vital to prevent memory leaks.
|
|
1939
1961
|
return () => {
|
|
1940
1962
|
container.removeEventListener("scroll", handleUserScroll);
|
|
1941
1963
|
};
|
|
1942
|
-
}, [totalCount, positions, stickToBottom]);
|
|
1964
|
+
}, [totalCount, positions, totalHeight, stickToBottom]);
|
|
1943
1965
|
|
|
1944
1966
|
const scrollToBottom = useCallback(
|
|
1945
1967
|
(behavior: ScrollBehavior = "smooth") => {
|