cogsbox-state 0.5.414 → 0.5.416
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 +109 -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,78 @@ 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
|
+
// Track if we're at bottom (with tolerance)
|
|
1956
|
+
wasAtBottomRef.current = distanceFromBottom < 100;
|
|
1957
|
+
|
|
1958
|
+
// Update visible range based on scroll position
|
|
1959
|
+
let startIndex = 0;
|
|
1960
|
+
for (let i = 0; i < positions.length; i++) {
|
|
1961
|
+
if (positions[i]! > scrollTop - itemHeight * overscan) {
|
|
1962
|
+
startIndex = Math.max(0, i - 1);
|
|
1963
|
+
break;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1917
1966
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
);
|
|
1967
|
+
let endIndex = startIndex;
|
|
1968
|
+
const viewportEnd = scrollTop + clientHeight;
|
|
1969
|
+
for (let i = startIndex; i < positions.length; i++) {
|
|
1970
|
+
if (positions[i]! > viewportEnd + itemHeight * overscan) {
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
endIndex = i;
|
|
1974
|
+
}
|
|
1927
1975
|
|
|
1928
1976
|
setRange({
|
|
1929
|
-
startIndex: Math.max(0,
|
|
1930
|
-
endIndex: Math.min(totalCount,
|
|
1977
|
+
startIndex: Math.max(0, startIndex),
|
|
1978
|
+
endIndex: Math.min(totalCount, endIndex + 1 + overscan),
|
|
1931
1979
|
});
|
|
1932
1980
|
};
|
|
1933
1981
|
|
|
1934
1982
|
container.addEventListener("scroll", handleScroll, {
|
|
1935
1983
|
passive: true,
|
|
1936
1984
|
});
|
|
1985
|
+
|
|
1986
|
+
// Initial setup
|
|
1987
|
+
if (stickToBottom && totalCount > 0) {
|
|
1988
|
+
// For initial load, jump to bottom
|
|
1989
|
+
container.scrollTop = container.scrollHeight;
|
|
1990
|
+
}
|
|
1937
1991
|
handleScroll();
|
|
1938
1992
|
|
|
1939
|
-
return () =>
|
|
1993
|
+
return () => {
|
|
1940
1994
|
container.removeEventListener("scroll", handleScroll);
|
|
1941
|
-
|
|
1995
|
+
};
|
|
1996
|
+
}, [positions, totalCount, itemHeight, overscan, stickToBottom]);
|
|
1942
1997
|
|
|
1943
1998
|
const scrollToBottom = useCallback(() => {
|
|
1944
|
-
|
|
1999
|
+
wasAtBottomRef.current = true;
|
|
2000
|
+
const scrolled = scrollToLastItem();
|
|
2001
|
+
if (!scrolled && containerRef.current) {
|
|
1945
2002
|
containerRef.current.scrollTop =
|
|
1946
2003
|
containerRef.current.scrollHeight;
|
|
1947
|
-
wasAtBottomRef.current = true;
|
|
1948
2004
|
}
|
|
1949
|
-
}, []);
|
|
2005
|
+
}, [scrollToLastItem]);
|
|
1950
2006
|
|
|
1951
2007
|
const scrollToIndex = useCallback(
|
|
1952
2008
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2009
|
+
const shadowArray =
|
|
2010
|
+
getGlobalStore
|
|
2011
|
+
.getState()
|
|
2012
|
+
.getShadowMetadata(stateKey, path) || [];
|
|
2013
|
+
const itemData = shadowArray[index];
|
|
2014
|
+
|
|
2015
|
+
if (itemData?.virtualizer?.domRef) {
|
|
2016
|
+
const element = itemData.virtualizer.domRef;
|
|
2017
|
+
if (element && element.scrollIntoView) {
|
|
2018
|
+
element.scrollIntoView({ behavior, block: "center" });
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// Fallback to position-based scrolling
|
|
1953
2024
|
if (containerRef.current && positions[index] !== undefined) {
|
|
1954
2025
|
containerRef.current.scrollTo({
|
|
1955
2026
|
top: positions[index],
|
|
@@ -1957,7 +2028,7 @@ function createProxyHandler<T>(
|
|
|
1957
2028
|
});
|
|
1958
2029
|
}
|
|
1959
2030
|
},
|
|
1960
|
-
[positions]
|
|
2031
|
+
[positions, stateKey, path]
|
|
1961
2032
|
);
|
|
1962
2033
|
|
|
1963
2034
|
const virtualizerProps = {
|