cogsbox-state 0.5.300 → 0.5.302
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 +415 -407
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +61 -63
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1803,7 +1803,7 @@ function createProxyHandler<T>(
|
|
|
1803
1803
|
options: VirtualViewOptions
|
|
1804
1804
|
): VirtualStateObjectResult<any[]> => {
|
|
1805
1805
|
const {
|
|
1806
|
-
itemHeight = 50,
|
|
1806
|
+
itemHeight = 50,
|
|
1807
1807
|
overscan = 5,
|
|
1808
1808
|
stickToBottom = false,
|
|
1809
1809
|
} = options;
|
|
@@ -1820,27 +1820,21 @@ function createProxyHandler<T>(
|
|
|
1820
1820
|
[]
|
|
1821
1821
|
);
|
|
1822
1822
|
|
|
1823
|
-
//
|
|
1823
|
+
// Track scroll position
|
|
1824
|
+
const isAtBottomRef = useRef(stickToBottom);
|
|
1825
|
+
const previousTotalCountRef = useRef(0);
|
|
1826
|
+
const isInitialMountRef = useRef(true);
|
|
1827
|
+
|
|
1824
1828
|
useEffect(() => {
|
|
1825
|
-
// Subscribe to shadow state changes for this specific key.
|
|
1826
1829
|
const unsubscribe = getGlobalStore
|
|
1827
1830
|
.getState()
|
|
1828
1831
|
.subscribeToShadowState(stateKey, forceRecalculate);
|
|
1829
|
-
|
|
1830
|
-
// On initial mount, we still need to trigger one recalculation
|
|
1831
|
-
// to capture heights from the very first render.
|
|
1832
1832
|
const timer = setTimeout(forceRecalculate, 50);
|
|
1833
|
-
|
|
1834
|
-
// Cleanup function to unsubscribe when the component unmounts.
|
|
1835
1833
|
return () => {
|
|
1836
1834
|
unsubscribe();
|
|
1837
1835
|
clearTimeout(timer);
|
|
1838
1836
|
};
|
|
1839
|
-
}, [stateKey, forceRecalculate]);
|
|
1840
|
-
|
|
1841
|
-
const isAtBottomRef = useRef(stickToBottom);
|
|
1842
|
-
const previousTotalCountRef = useRef(0);
|
|
1843
|
-
const isInitialMountRef = useRef(true);
|
|
1837
|
+
}, [stateKey, forceRecalculate]);
|
|
1844
1838
|
|
|
1845
1839
|
const sourceArray = getGlobalStore().getNestedState(
|
|
1846
1840
|
stateKey,
|
|
@@ -1875,52 +1869,46 @@ function createProxyHandler<T>(
|
|
|
1875
1869
|
...meta,
|
|
1876
1870
|
validIndices,
|
|
1877
1871
|
});
|
|
1878
|
-
}, [range.startIndex, range.endIndex, sourceArray]);
|
|
1872
|
+
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1873
|
+
|
|
1879
1874
|
useLayoutEffect(() => {
|
|
1880
1875
|
const container = containerRef.current;
|
|
1881
1876
|
if (!container) return;
|
|
1882
1877
|
|
|
1878
|
+
const wasAtBottom = isAtBottomRef.current;
|
|
1883
1879
|
const listGrew = totalCount > previousTotalCountRef.current;
|
|
1884
|
-
|
|
1885
|
-
// --- THE FIX for BOTH initial load and new entries ---
|
|
1886
|
-
// We capture the scroll position *before* we do anything else.
|
|
1887
|
-
// We also check if we are VERY close to the bottom. This will be true
|
|
1888
|
-
// on initial load (0 scrollHeight, 0 scrollTop) and when user is at the end.
|
|
1889
|
-
const wasAtBottom =
|
|
1890
|
-
container.scrollHeight -
|
|
1891
|
-
container.scrollTop -
|
|
1892
|
-
container.clientHeight <
|
|
1893
|
-
5;
|
|
1894
|
-
|
|
1895
|
-
// Now we update the ref for the *next* render cycle.
|
|
1896
1880
|
previousTotalCountRef.current = totalCount;
|
|
1897
1881
|
|
|
1898
1882
|
const handleScroll = () => {
|
|
1899
|
-
// ... (binary search logic to setRange is the same) ...
|
|
1900
1883
|
const { scrollTop, clientHeight, scrollHeight } = container;
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1884
|
+
// Consider "at bottom" if within 10px
|
|
1885
|
+
isAtBottomRef.current =
|
|
1886
|
+
scrollHeight - scrollTop - clientHeight < 10;
|
|
1887
|
+
|
|
1888
|
+
// Binary search for start index
|
|
1889
|
+
let low = 0,
|
|
1890
|
+
high = totalCount - 1;
|
|
1891
|
+
while (low <= high) {
|
|
1892
|
+
const mid = Math.floor((low + high) / 2);
|
|
1893
|
+
if (positions[mid]! < scrollTop) {
|
|
1894
|
+
low = mid + 1;
|
|
1895
|
+
} else {
|
|
1896
|
+
high = mid - 1;
|
|
1911
1897
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1898
|
+
}
|
|
1899
|
+
const startIndex = Math.max(0, high - overscan);
|
|
1900
|
+
|
|
1901
|
+
// Find end index
|
|
1915
1902
|
let endIndex = startIndex;
|
|
1903
|
+
const visibleEnd = scrollTop + clientHeight;
|
|
1916
1904
|
while (
|
|
1917
1905
|
endIndex < totalCount &&
|
|
1918
|
-
positions[endIndex]! <
|
|
1906
|
+
positions[endIndex]! < visibleEnd
|
|
1919
1907
|
) {
|
|
1920
1908
|
endIndex++;
|
|
1921
1909
|
}
|
|
1922
|
-
startIndex = Math.max(0, startIndex - overscan);
|
|
1923
1910
|
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1911
|
+
|
|
1924
1912
|
setRange((prevRange) => {
|
|
1925
1913
|
if (
|
|
1926
1914
|
prevRange.startIndex !== startIndex ||
|
|
@@ -1935,28 +1923,35 @@ function createProxyHandler<T>(
|
|
|
1935
1923
|
container.addEventListener("scroll", handleScroll, {
|
|
1936
1924
|
passive: true,
|
|
1937
1925
|
});
|
|
1938
|
-
handleScroll();
|
|
1939
1926
|
|
|
1940
|
-
//
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1927
|
+
// Handle stick to bottom
|
|
1928
|
+
if (stickToBottom) {
|
|
1929
|
+
if (isInitialMountRef.current) {
|
|
1930
|
+
// First render - go to bottom instantly
|
|
1931
|
+
container.scrollTo({
|
|
1932
|
+
top: container.scrollHeight,
|
|
1933
|
+
behavior: "auto",
|
|
1934
|
+
});
|
|
1935
|
+
} else if (wasAtBottom && listGrew) {
|
|
1936
|
+
// New items added and we were at bottom - stay at bottom
|
|
1937
|
+
requestAnimationFrame(() => {
|
|
1938
|
+
container.scrollTo({
|
|
1939
|
+
top: container.scrollHeight,
|
|
1940
|
+
behavior: "smooth",
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1950
1944
|
}
|
|
1951
1945
|
|
|
1952
|
-
//
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1946
|
+
// Mark as no longer initial mount after first render
|
|
1947
|
+
isInitialMountRef.current = false;
|
|
1948
|
+
|
|
1949
|
+
// Run handleScroll once to set initial range
|
|
1950
|
+
handleScroll();
|
|
1956
1951
|
|
|
1957
1952
|
return () =>
|
|
1958
1953
|
container.removeEventListener("scroll", handleScroll);
|
|
1959
|
-
}, [totalCount, overscan, stickToBottom
|
|
1954
|
+
}, [totalCount, positions, overscan, stickToBottom]);
|
|
1960
1955
|
|
|
1961
1956
|
const scrollToBottom = useCallback(
|
|
1962
1957
|
(behavior: ScrollBehavior = "smooth") => {
|
|
@@ -1972,9 +1967,9 @@ function createProxyHandler<T>(
|
|
|
1972
1967
|
|
|
1973
1968
|
const scrollToIndex = useCallback(
|
|
1974
1969
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1975
|
-
if (containerRef.current) {
|
|
1970
|
+
if (containerRef.current && positions[index] !== undefined) {
|
|
1976
1971
|
containerRef.current.scrollTo({
|
|
1977
|
-
top: positions[index]
|
|
1972
|
+
top: positions[index],
|
|
1978
1973
|
behavior,
|
|
1979
1974
|
});
|
|
1980
1975
|
}
|
|
@@ -1985,10 +1980,13 @@ function createProxyHandler<T>(
|
|
|
1985
1980
|
const virtualizerProps = {
|
|
1986
1981
|
outer: {
|
|
1987
1982
|
ref: containerRef,
|
|
1988
|
-
style: { overflowY: "auto", height: "100%" },
|
|
1983
|
+
style: { overflowY: "auto" as const, height: "100%" },
|
|
1989
1984
|
},
|
|
1990
1985
|
inner: {
|
|
1991
|
-
style: {
|
|
1986
|
+
style: {
|
|
1987
|
+
height: `${totalHeight}px`,
|
|
1988
|
+
position: "relative" as const,
|
|
1989
|
+
},
|
|
1992
1990
|
},
|
|
1993
1991
|
list: {
|
|
1994
1992
|
style: {
|
|
@@ -1999,7 +1997,7 @@ function createProxyHandler<T>(
|
|
|
1999
1997
|
|
|
2000
1998
|
return {
|
|
2001
1999
|
virtualState,
|
|
2002
|
-
virtualizerProps
|
|
2000
|
+
virtualizerProps,
|
|
2003
2001
|
scrollToBottom,
|
|
2004
2002
|
scrollToIndex,
|
|
2005
2003
|
};
|