cogsbox-state 0.5.339 → 0.5.342
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 +515 -492
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +62 -67
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.
|
|
1872
|
+
|
|
1873
|
+
// This layout effect is for SCROLLING and RANGE updates ONLY.
|
|
1898
1874
|
useLayoutEffect(() => {
|
|
1899
1875
|
const container = containerRef.current;
|
|
1900
1876
|
if (!container) return;
|
|
1901
1877
|
|
|
1902
|
-
|
|
1903
|
-
// We check this now, before the new items might have pushed the scrollbar up.
|
|
1904
|
-
const wasScrolledToBottom =
|
|
1905
|
-
container.scrollHeight -
|
|
1906
|
-
container.scrollTop -
|
|
1907
|
-
container.clientHeight <
|
|
1908
|
-
itemHeight;
|
|
1878
|
+
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1909
1879
|
|
|
1910
1880
|
// This function determines what's visible in the viewport.
|
|
1911
1881
|
const updateVirtualRange = () => {
|
|
1912
1882
|
if (!container) return;
|
|
1913
1883
|
const { scrollTop, clientHeight } = container;
|
|
1914
|
-
|
|
1915
|
-
// Find the first visible item
|
|
1916
1884
|
let low = 0,
|
|
1917
1885
|
high = totalCount - 1;
|
|
1918
1886
|
while (low <= high) {
|
|
@@ -1921,8 +1889,6 @@ function createProxyHandler<T>(
|
|
|
1921
1889
|
else high = mid - 1;
|
|
1922
1890
|
}
|
|
1923
1891
|
const startIndex = Math.max(0, high - overscan);
|
|
1924
|
-
|
|
1925
|
-
// Find the last visible item
|
|
1926
1892
|
let endIndex = startIndex;
|
|
1927
1893
|
const visibleEnd = scrollTop + clientHeight;
|
|
1928
1894
|
while (
|
|
@@ -1932,50 +1898,78 @@ function createProxyHandler<T>(
|
|
|
1932
1898
|
endIndex++;
|
|
1933
1899
|
}
|
|
1934
1900
|
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1935
|
-
|
|
1936
|
-
// Update the state to render the correct slice
|
|
1937
1901
|
setRange({ startIndex, endIndex });
|
|
1938
1902
|
};
|
|
1939
1903
|
|
|
1940
|
-
//
|
|
1904
|
+
// If new items were added AND we are locked to the bottom, scroll smoothly.
|
|
1905
|
+
if (
|
|
1906
|
+
stickToBottom &&
|
|
1907
|
+
hasNewItems &&
|
|
1908
|
+
isLockedToBottomRef.current
|
|
1909
|
+
) {
|
|
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);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// Always update the visible range.
|
|
1925
|
+
updateVirtualRange();
|
|
1926
|
+
}, [
|
|
1927
|
+
totalCount,
|
|
1928
|
+
positions,
|
|
1929
|
+
totalHeight,
|
|
1930
|
+
stickToBottom,
|
|
1931
|
+
itemHeight,
|
|
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
|
+
|
|
1941
1944
|
const handleUserScroll = () => {
|
|
1942
|
-
|
|
1943
|
-
|
|
1945
|
+
if (!container) return;
|
|
1946
|
+
// If the user scrolls up, break the lock.
|
|
1947
|
+
const isAtBottom =
|
|
1944
1948
|
container.scrollHeight -
|
|
1945
1949
|
container.scrollTop -
|
|
1946
1950
|
container.clientHeight <
|
|
1947
1951
|
1;
|
|
1948
|
-
|
|
1949
|
-
|
|
1952
|
+
if (!isAtBottom) {
|
|
1953
|
+
isLockedToBottomRef.current = false;
|
|
1954
|
+
}
|
|
1950
1955
|
};
|
|
1951
1956
|
|
|
1952
|
-
// Add the listener for user scrolling.
|
|
1953
1957
|
container.addEventListener("scroll", handleUserScroll, {
|
|
1954
1958
|
passive: true,
|
|
1955
1959
|
});
|
|
1956
|
-
|
|
1957
|
-
// --- STEP 2: Apply the scroll AFTER the render, based on what we remembered ---
|
|
1958
|
-
if (
|
|
1959
|
-
stickToBottom &&
|
|
1960
|
-
(isLockedToBottomRef.current || wasScrolledToBottom)
|
|
1961
|
-
) {
|
|
1962
|
-
// If we are "locked" OR if we were at the bottom just before this render,
|
|
1963
|
-
// then scroll to the new bottom. This handles both initial load and new items.
|
|
1964
|
-
container.scrollTop = container.scrollHeight;
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
// Always calculate the visible range after any potential scroll changes.
|
|
1968
|
-
updateVirtualRange();
|
|
1969
|
-
|
|
1970
|
-
// Cleanup function is vital to prevent memory leaks.
|
|
1971
|
-
return () => {
|
|
1960
|
+
return () =>
|
|
1972
1961
|
container.removeEventListener("scroll", handleUserScroll);
|
|
1973
|
-
|
|
1974
|
-
|
|
1962
|
+
}, []); // This runs only once on mount.
|
|
1963
|
+
|
|
1964
|
+
// After every render, update the previous count ref.
|
|
1965
|
+
useEffect(() => {
|
|
1966
|
+
prevTotalCountRef.current = totalCount;
|
|
1967
|
+
});
|
|
1975
1968
|
|
|
1976
1969
|
const scrollToBottom = useCallback(
|
|
1977
1970
|
(behavior: ScrollBehavior = "smooth") => {
|
|
1978
1971
|
if (containerRef.current) {
|
|
1972
|
+
// Re-enable the lock when the user explicitly asks to scroll to bottom.
|
|
1979
1973
|
isLockedToBottomRef.current = true;
|
|
1980
1974
|
containerRef.current.scrollTo({
|
|
1981
1975
|
top: containerRef.current.scrollHeight,
|
|
@@ -1989,6 +1983,7 @@ function createProxyHandler<T>(
|
|
|
1989
1983
|
const scrollToIndex = useCallback(
|
|
1990
1984
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1991
1985
|
if (containerRef.current && positions[index] !== undefined) {
|
|
1986
|
+
// Scrolling to a specific index should break the lock.
|
|
1992
1987
|
isLockedToBottomRef.current = false;
|
|
1993
1988
|
containerRef.current.scrollTo({
|
|
1994
1989
|
top: positions[index],
|