cogsbox-state 0.5.415 → 0.5.417
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 +730 -691
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +110 -38
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1821,7 +1821,7 @@ function createProxyHandler<T>(
|
|
|
1821
1821
|
endIndex: 10,
|
|
1822
1822
|
});
|
|
1823
1823
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1824
|
-
const wasAtBottomRef = useRef(
|
|
1824
|
+
const wasAtBottomRef = useRef(true);
|
|
1825
1825
|
const previousCountRef = useRef(0);
|
|
1826
1826
|
|
|
1827
1827
|
// Subscribe to shadow state updates
|
|
@@ -1832,7 +1832,7 @@ function createProxyHandler<T>(
|
|
|
1832
1832
|
setShadowUpdateTrigger((prev) => prev + 1);
|
|
1833
1833
|
});
|
|
1834
1834
|
return unsubscribe;
|
|
1835
|
-
}, []);
|
|
1835
|
+
}, [stateKey]);
|
|
1836
1836
|
|
|
1837
1837
|
const sourceArray = getGlobalStore().getNestedState(
|
|
1838
1838
|
stateKey,
|
|
@@ -1854,7 +1854,13 @@ function createProxyHandler<T>(
|
|
|
1854
1854
|
height += measuredHeight || itemHeight;
|
|
1855
1855
|
}
|
|
1856
1856
|
return { totalHeight: height, positions: pos };
|
|
1857
|
-
}, [
|
|
1857
|
+
}, [
|
|
1858
|
+
totalCount,
|
|
1859
|
+
stateKey,
|
|
1860
|
+
path.join("."),
|
|
1861
|
+
itemHeight,
|
|
1862
|
+
shadowUpdateTrigger,
|
|
1863
|
+
]);
|
|
1858
1864
|
|
|
1859
1865
|
// Create virtual state
|
|
1860
1866
|
const virtualState = useMemo(() => {
|
|
@@ -1869,43 +1875,72 @@ function createProxyHandler<T>(
|
|
|
1869
1875
|
...meta,
|
|
1870
1876
|
validIndices,
|
|
1871
1877
|
});
|
|
1872
|
-
}, [range.startIndex, range.endIndex, sourceArray]);
|
|
1878
|
+
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1879
|
+
|
|
1880
|
+
// Helper to scroll to last item using stored ref
|
|
1881
|
+
const scrollToLastItem = useCallback(() => {
|
|
1882
|
+
const shadowArray =
|
|
1883
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
1884
|
+
[];
|
|
1885
|
+
const lastIndex = totalCount - 1;
|
|
1886
|
+
|
|
1887
|
+
if (lastIndex >= 0) {
|
|
1888
|
+
const lastItemData = shadowArray[lastIndex];
|
|
1889
|
+
if (lastItemData?.virtualizer?.domRef) {
|
|
1890
|
+
const element = lastItemData.virtualizer.domRef;
|
|
1891
|
+
if (element && element.scrollIntoView) {
|
|
1892
|
+
element.scrollIntoView({
|
|
1893
|
+
behavior: "auto",
|
|
1894
|
+
block: "end",
|
|
1895
|
+
inline: "nearest",
|
|
1896
|
+
});
|
|
1897
|
+
return true;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return false;
|
|
1902
|
+
}, [stateKey, path, totalCount]);
|
|
1873
1903
|
|
|
1874
1904
|
// Handle new items when at bottom
|
|
1875
1905
|
useEffect(() => {
|
|
1876
1906
|
if (!stickToBottom || totalCount === 0) return;
|
|
1877
1907
|
|
|
1878
1908
|
const hasNewItems = totalCount > previousCountRef.current;
|
|
1909
|
+
const isInitialLoad =
|
|
1910
|
+
previousCountRef.current === 0 && totalCount > 0;
|
|
1879
1911
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
hasNewItems &&
|
|
1883
|
-
wasAtBottomRef.current &&
|
|
1884
|
-
previousCountRef.current > 0
|
|
1885
|
-
) {
|
|
1886
|
-
const container = containerRef.current;
|
|
1887
|
-
if (!container) return;
|
|
1888
|
-
|
|
1889
|
-
// Update range to show end
|
|
1912
|
+
if ((hasNewItems || isInitialLoad) && wasAtBottomRef.current) {
|
|
1913
|
+
// First, ensure the last items are in range
|
|
1890
1914
|
const visibleCount = Math.ceil(
|
|
1891
|
-
|
|
1915
|
+
containerRef.current?.clientHeight || 0 / itemHeight
|
|
1892
1916
|
);
|
|
1893
|
-
|
|
1917
|
+
const newRange = {
|
|
1894
1918
|
startIndex: Math.max(
|
|
1895
1919
|
0,
|
|
1896
1920
|
totalCount - visibleCount - overscan
|
|
1897
1921
|
),
|
|
1898
1922
|
endIndex: totalCount,
|
|
1899
|
-
}
|
|
1923
|
+
};
|
|
1924
|
+
|
|
1925
|
+
setRange(newRange);
|
|
1900
1926
|
|
|
1901
|
-
//
|
|
1902
|
-
setTimeout(() => {
|
|
1903
|
-
|
|
1927
|
+
// Then scroll to the last item after it renders
|
|
1928
|
+
const timeoutId = setTimeout(() => {
|
|
1929
|
+
const scrolled = scrollToLastItem();
|
|
1930
|
+
if (!scrolled && containerRef.current) {
|
|
1931
|
+
// Fallback if ref not available yet
|
|
1932
|
+
containerRef.current.scrollTop =
|
|
1933
|
+
containerRef.current.scrollHeight;
|
|
1934
|
+
}
|
|
1904
1935
|
}, 50);
|
|
1936
|
+
|
|
1937
|
+
previousCountRef.current = totalCount;
|
|
1938
|
+
|
|
1939
|
+
return () => clearTimeout(timeoutId);
|
|
1905
1940
|
}
|
|
1906
1941
|
|
|
1907
1942
|
previousCountRef.current = totalCount;
|
|
1908
|
-
}, [totalCount]);
|
|
1943
|
+
}, [totalCount]);
|
|
1909
1944
|
|
|
1910
1945
|
// Handle scroll events
|
|
1911
1946
|
useEffect(() => {
|
|
@@ -1914,42 +1949,79 @@ function createProxyHandler<T>(
|
|
|
1914
1949
|
|
|
1915
1950
|
const handleScroll = () => {
|
|
1916
1951
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
1952
|
+
const distanceFromBottom =
|
|
1953
|
+
scrollHeight - scrollTop - clientHeight;
|
|
1954
|
+
|
|
1955
|
+
// Only consider "at bottom" if we're VERY close (like 5px)
|
|
1956
|
+
// This prevents the snap-back behavior when scrolling up
|
|
1957
|
+
wasAtBottomRef.current = distanceFromBottom < 5;
|
|
1958
|
+
|
|
1959
|
+
// Update visible range based on scroll position
|
|
1960
|
+
let startIndex = 0;
|
|
1961
|
+
for (let i = 0; i < positions.length; i++) {
|
|
1962
|
+
if (positions[i]! > scrollTop - itemHeight * overscan) {
|
|
1963
|
+
startIndex = Math.max(0, i - 1);
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1917
1967
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
);
|
|
1968
|
+
let endIndex = startIndex;
|
|
1969
|
+
const viewportEnd = scrollTop + clientHeight;
|
|
1970
|
+
for (let i = startIndex; i < positions.length; i++) {
|
|
1971
|
+
if (positions[i]! > viewportEnd + itemHeight * overscan) {
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
endIndex = i;
|
|
1975
|
+
}
|
|
1927
1976
|
|
|
1928
1977
|
setRange({
|
|
1929
|
-
startIndex: Math.max(0,
|
|
1930
|
-
endIndex: Math.min(totalCount,
|
|
1978
|
+
startIndex: Math.max(0, startIndex),
|
|
1979
|
+
endIndex: Math.min(totalCount, endIndex + 1 + overscan),
|
|
1931
1980
|
});
|
|
1932
1981
|
};
|
|
1933
1982
|
|
|
1934
1983
|
container.addEventListener("scroll", handleScroll, {
|
|
1935
1984
|
passive: true,
|
|
1936
1985
|
});
|
|
1986
|
+
|
|
1987
|
+
// Initial setup
|
|
1988
|
+
if (stickToBottom && totalCount > 0) {
|
|
1989
|
+
// For initial load, jump to bottom
|
|
1990
|
+
container.scrollTop = container.scrollHeight;
|
|
1991
|
+
}
|
|
1937
1992
|
handleScroll();
|
|
1938
1993
|
|
|
1939
|
-
return () =>
|
|
1994
|
+
return () => {
|
|
1940
1995
|
container.removeEventListener("scroll", handleScroll);
|
|
1941
|
-
|
|
1996
|
+
};
|
|
1997
|
+
}, [positions, totalCount, itemHeight, overscan, stickToBottom]);
|
|
1942
1998
|
|
|
1943
1999
|
const scrollToBottom = useCallback(() => {
|
|
1944
|
-
|
|
2000
|
+
wasAtBottomRef.current = true;
|
|
2001
|
+
const scrolled = scrollToLastItem();
|
|
2002
|
+
if (!scrolled && containerRef.current) {
|
|
1945
2003
|
containerRef.current.scrollTop =
|
|
1946
2004
|
containerRef.current.scrollHeight;
|
|
1947
|
-
wasAtBottomRef.current = true;
|
|
1948
2005
|
}
|
|
1949
|
-
}, []);
|
|
2006
|
+
}, [scrollToLastItem]);
|
|
1950
2007
|
|
|
1951
2008
|
const scrollToIndex = useCallback(
|
|
1952
2009
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2010
|
+
const shadowArray =
|
|
2011
|
+
getGlobalStore
|
|
2012
|
+
.getState()
|
|
2013
|
+
.getShadowMetadata(stateKey, path) || [];
|
|
2014
|
+
const itemData = shadowArray[index];
|
|
2015
|
+
|
|
2016
|
+
if (itemData?.virtualizer?.domRef) {
|
|
2017
|
+
const element = itemData.virtualizer.domRef;
|
|
2018
|
+
if (element && element.scrollIntoView) {
|
|
2019
|
+
element.scrollIntoView({ behavior, block: "center" });
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// Fallback to position-based scrolling
|
|
1953
2025
|
if (containerRef.current && positions[index] !== undefined) {
|
|
1954
2026
|
containerRef.current.scrollTo({
|
|
1955
2027
|
top: positions[index],
|
|
@@ -1957,7 +2029,7 @@ function createProxyHandler<T>(
|
|
|
1957
2029
|
});
|
|
1958
2030
|
}
|
|
1959
2031
|
},
|
|
1960
|
-
[positions]
|
|
2032
|
+
[positions, stateKey, path]
|
|
1961
2033
|
);
|
|
1962
2034
|
|
|
1963
2035
|
const virtualizerProps = {
|