cogsbox-state 0.5.313 → 0.5.314
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 +546 -542
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +48 -45
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1814,12 +1814,10 @@ 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
1818
|
const isLockedToBottomRef = useRef(stickToBottom);
|
|
1819
|
-
// This ref tracks the item count to determine when to scroll smoothly vs instantly.
|
|
1820
|
-
const previousTotalCountRef = useRef(0);
|
|
1821
1819
|
|
|
1822
|
-
//
|
|
1820
|
+
// This state triggers a re-render when item heights change.
|
|
1823
1821
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1824
1822
|
|
|
1825
1823
|
useEffect(() => {
|
|
@@ -1837,7 +1835,7 @@ function createProxyHandler<T>(
|
|
|
1837
1835
|
) as any[];
|
|
1838
1836
|
const totalCount = sourceArray.length;
|
|
1839
1837
|
|
|
1840
|
-
// Calculate heights
|
|
1838
|
+
// Calculate heights from shadow state. This runs when data or measurements change.
|
|
1841
1839
|
const { totalHeight, positions } = useMemo(() => {
|
|
1842
1840
|
const shadowArray =
|
|
1843
1841
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
@@ -1859,6 +1857,7 @@ function createProxyHandler<T>(
|
|
|
1859
1857
|
shadowUpdateTrigger,
|
|
1860
1858
|
]);
|
|
1861
1859
|
|
|
1860
|
+
// Memoize the virtualized slice of data.
|
|
1862
1861
|
const virtualState = useMemo(() => {
|
|
1863
1862
|
const start = Math.max(0, range.startIndex);
|
|
1864
1863
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1873,19 +1872,17 @@ function createProxyHandler<T>(
|
|
|
1873
1872
|
});
|
|
1874
1873
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1875
1874
|
|
|
1875
|
+
// This is the main effect that handles all scrolling and updates.
|
|
1876
1876
|
useLayoutEffect(() => {
|
|
1877
1877
|
const container = containerRef.current;
|
|
1878
1878
|
if (!container) return;
|
|
1879
1879
|
|
|
1880
|
-
|
|
1880
|
+
let scrollTimeoutId: NodeJS.Timeout;
|
|
1881
1881
|
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
isLockedToBottomRef.current = isNowAtBottom;
|
|
1887
|
-
|
|
1888
|
-
// ... (virtualization range calculation logic is unchanged)
|
|
1882
|
+
// This function determines what's visible in the viewport.
|
|
1883
|
+
const updateVirtualRange = () => {
|
|
1884
|
+
if (!container) return;
|
|
1885
|
+
const { scrollTop } = container;
|
|
1889
1886
|
let low = 0,
|
|
1890
1887
|
high = totalCount - 1;
|
|
1891
1888
|
while (low <= high) {
|
|
@@ -1895,7 +1892,7 @@ function createProxyHandler<T>(
|
|
|
1895
1892
|
}
|
|
1896
1893
|
const startIndex = Math.max(0, high - overscan);
|
|
1897
1894
|
let endIndex = startIndex;
|
|
1898
|
-
const visibleEnd = scrollTop + clientHeight;
|
|
1895
|
+
const visibleEnd = scrollTop + container.clientHeight;
|
|
1899
1896
|
while (
|
|
1900
1897
|
endIndex < totalCount &&
|
|
1901
1898
|
positions[endIndex]! < visibleEnd
|
|
@@ -1903,49 +1900,54 @@ function createProxyHandler<T>(
|
|
|
1903
1900
|
endIndex++;
|
|
1904
1901
|
}
|
|
1905
1902
|
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1906
|
-
setRange(
|
|
1907
|
-
if (
|
|
1908
|
-
prevRange.startIndex !== startIndex ||
|
|
1909
|
-
prevRange.endIndex !== endIndex
|
|
1910
|
-
) {
|
|
1911
|
-
return { startIndex, endIndex };
|
|
1912
|
-
}
|
|
1913
|
-
return prevRange;
|
|
1914
|
-
});
|
|
1903
|
+
setRange({ startIndex, endIndex });
|
|
1915
1904
|
};
|
|
1916
1905
|
|
|
1917
|
-
|
|
1906
|
+
// This function handles ONLY user-initiated scrolls.
|
|
1907
|
+
const handleUserScroll = () => {
|
|
1908
|
+
isLockedToBottomRef.current =
|
|
1909
|
+
container.scrollHeight -
|
|
1910
|
+
container.scrollTop -
|
|
1911
|
+
container.clientHeight <
|
|
1912
|
+
1;
|
|
1913
|
+
updateVirtualRange();
|
|
1914
|
+
};
|
|
1915
|
+
|
|
1916
|
+
container.addEventListener("scroll", handleUserScroll, {
|
|
1918
1917
|
passive: true,
|
|
1919
1918
|
});
|
|
1920
1919
|
|
|
1921
|
-
// ---
|
|
1922
|
-
if (stickToBottom
|
|
1923
|
-
//
|
|
1924
|
-
//
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
});
|
|
1920
|
+
// --- THE CORE FIX ---
|
|
1921
|
+
if (stickToBottom) {
|
|
1922
|
+
// We use a timeout to wait for React to render AND for useMeasure to update heights.
|
|
1923
|
+
// This is the CRUCIAL part that fixes the race condition.
|
|
1924
|
+
scrollTimeoutId = setTimeout(() => {
|
|
1925
|
+
// By the time this runs, `container.scrollHeight` is accurate.
|
|
1926
|
+
// We only scroll if the user hasn't manually scrolled up in the meantime.
|
|
1927
|
+
if (isLockedToBottomRef.current) {
|
|
1928
|
+
container.scrollTo({
|
|
1929
|
+
top: container.scrollHeight,
|
|
1930
|
+
behavior: "auto", // ALWAYS 'auto' for an instant, correct jump.
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
}, 50); // A small 50ms delay is a robust buffer.
|
|
1935
1934
|
}
|
|
1936
1935
|
|
|
1937
|
-
// Update the
|
|
1938
|
-
|
|
1936
|
+
// Update the visible range on initial load.
|
|
1937
|
+
updateVirtualRange();
|
|
1939
1938
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
container.removeEventListener("scroll",
|
|
1944
|
-
|
|
1939
|
+
// Cleanup function is vital to prevent memory leaks.
|
|
1940
|
+
return () => {
|
|
1941
|
+
clearTimeout(scrollTimeoutId);
|
|
1942
|
+
container.removeEventListener("scroll", handleUserScroll);
|
|
1943
|
+
};
|
|
1944
|
+
// This effect re-runs whenever the list size or item heights change.
|
|
1945
|
+
}, [totalCount, positions, stickToBottom]);
|
|
1945
1946
|
|
|
1946
1947
|
const scrollToBottom = useCallback(
|
|
1947
1948
|
(behavior: ScrollBehavior = "smooth") => {
|
|
1948
1949
|
if (containerRef.current) {
|
|
1950
|
+
isLockedToBottomRef.current = true;
|
|
1949
1951
|
containerRef.current.scrollTo({
|
|
1950
1952
|
top: containerRef.current.scrollHeight,
|
|
1951
1953
|
behavior,
|
|
@@ -1958,6 +1960,7 @@ function createProxyHandler<T>(
|
|
|
1958
1960
|
const scrollToIndex = useCallback(
|
|
1959
1961
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1960
1962
|
if (containerRef.current && positions[index] !== undefined) {
|
|
1963
|
+
isLockedToBottomRef.current = false;
|
|
1961
1964
|
containerRef.current.scrollTo({
|
|
1962
1965
|
top: positions[index],
|
|
1963
1966
|
behavior,
|