cogsbox-state 0.5.321 → 0.5.323
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 +531 -550
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +56 -84
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1813,15 +1813,12 @@ 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
1818
|
const isLockedToBottomRef = useRef(stickToBottom);
|
|
1817
|
-
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1818
|
-
const hasScrolledToBottomRef = useRef(false);
|
|
1819
1819
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
path
|
|
1823
|
-
) as any[];
|
|
1824
|
-
const totalCount = sourceArray.length;
|
|
1820
|
+
// This state triggers a re-render when item heights change.
|
|
1821
|
+
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1825
1822
|
|
|
1826
1823
|
useEffect(() => {
|
|
1827
1824
|
const unsubscribe = getGlobalStore
|
|
@@ -1832,52 +1829,35 @@ function createProxyHandler<T>(
|
|
|
1832
1829
|
return unsubscribe;
|
|
1833
1830
|
}, [stateKey]);
|
|
1834
1831
|
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
.getShadowMetadata(stateKey, path) || [];
|
|
1841
|
-
let height = 0;
|
|
1842
|
-
const pos: number[] = [];
|
|
1843
|
-
let bottomMeasuredCount = 0;
|
|
1844
|
-
|
|
1845
|
-
// Check how many of the last 20 items are measured
|
|
1846
|
-
const checkFromIndex = Math.max(0, totalCount - 20);
|
|
1847
|
-
|
|
1848
|
-
for (let i = 0; i < totalCount; i++) {
|
|
1849
|
-
pos[i] = height;
|
|
1850
|
-
const measuredHeight =
|
|
1851
|
-
shadowArray[i]?.virtualizer?.itemHeight;
|
|
1852
|
-
|
|
1853
|
-
if (measuredHeight) {
|
|
1854
|
-
height += measuredHeight;
|
|
1855
|
-
if (i >= checkFromIndex) {
|
|
1856
|
-
bottomMeasuredCount++;
|
|
1857
|
-
}
|
|
1858
|
-
} else {
|
|
1859
|
-
height += itemHeight;
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1832
|
+
const sourceArray = getGlobalStore().getNestedState(
|
|
1833
|
+
stateKey,
|
|
1834
|
+
path
|
|
1835
|
+
) as any[];
|
|
1836
|
+
const totalCount = sourceArray.length;
|
|
1862
1837
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1838
|
+
// Calculate heights from shadow state. This runs when data or measurements change.
|
|
1839
|
+
const { totalHeight, positions } = useMemo(() => {
|
|
1840
|
+
const shadowArray =
|
|
1841
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
1842
|
+
[];
|
|
1843
|
+
let height = 0;
|
|
1844
|
+
const pos: number[] = [];
|
|
1845
|
+
for (let i = 0; i < totalCount; i++) {
|
|
1846
|
+
pos[i] = height;
|
|
1847
|
+
const measuredHeight =
|
|
1848
|
+
shadowArray[i]?.virtualizer?.itemHeight;
|
|
1849
|
+
height += measuredHeight || itemHeight;
|
|
1850
|
+
}
|
|
1851
|
+
return { totalHeight: height, positions: pos };
|
|
1852
|
+
}, [
|
|
1853
|
+
totalCount,
|
|
1854
|
+
stateKey,
|
|
1855
|
+
path.join("."),
|
|
1856
|
+
itemHeight,
|
|
1857
|
+
shadowUpdateTrigger,
|
|
1858
|
+
]);
|
|
1880
1859
|
|
|
1860
|
+
// Memoize the virtualized slice of data.
|
|
1881
1861
|
const virtualState = useMemo(() => {
|
|
1882
1862
|
const start = Math.max(0, range.startIndex);
|
|
1883
1863
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1892,10 +1872,14 @@ function createProxyHandler<T>(
|
|
|
1892
1872
|
});
|
|
1893
1873
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1894
1874
|
|
|
1875
|
+
// This is the main effect that handles all scrolling and updates.
|
|
1895
1876
|
useLayoutEffect(() => {
|
|
1896
1877
|
const container = containerRef.current;
|
|
1897
1878
|
if (!container) return;
|
|
1898
1879
|
|
|
1880
|
+
let scrollTimeoutId: NodeJS.Timeout;
|
|
1881
|
+
|
|
1882
|
+
// This function determines what's visible in the viewport.
|
|
1899
1883
|
const updateVirtualRange = () => {
|
|
1900
1884
|
if (!container) return;
|
|
1901
1885
|
const { scrollTop } = container;
|
|
@@ -1919,6 +1903,7 @@ function createProxyHandler<T>(
|
|
|
1919
1903
|
setRange({ startIndex, endIndex });
|
|
1920
1904
|
};
|
|
1921
1905
|
|
|
1906
|
+
// This function handles ONLY user-initiated scrolls.
|
|
1922
1907
|
const handleUserScroll = () => {
|
|
1923
1908
|
isLockedToBottomRef.current =
|
|
1924
1909
|
container.scrollHeight -
|
|
@@ -1932,39 +1917,32 @@ function createProxyHandler<T>(
|
|
|
1932
1917
|
passive: true,
|
|
1933
1918
|
});
|
|
1934
1919
|
|
|
1935
|
-
//
|
|
1936
|
-
if (
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
);
|
|
1950
|
-
container.scrollTop = jumpPosition;
|
|
1951
|
-
} else {
|
|
1952
|
-
// Step 2: Bottom items are measured, now scroll to actual bottom
|
|
1953
|
-
console.log(
|
|
1954
|
-
"[VirtualView] Bottom items measured, scrolling to true bottom"
|
|
1955
|
-
);
|
|
1956
|
-
hasScrolledToBottomRef.current = true;
|
|
1957
|
-
container.scrollTop = container.scrollHeight;
|
|
1958
|
-
isLockedToBottomRef.current = true;
|
|
1959
|
-
}
|
|
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
|
+
}, 1000); // A small 50ms delay is a robust buffer.
|
|
1960
1934
|
}
|
|
1961
1935
|
|
|
1936
|
+
// Update the visible range on initial load.
|
|
1962
1937
|
updateVirtualRange();
|
|
1963
1938
|
|
|
1939
|
+
// Cleanup function is vital to prevent memory leaks.
|
|
1964
1940
|
return () => {
|
|
1941
|
+
clearTimeout(scrollTimeoutId);
|
|
1965
1942
|
container.removeEventListener("scroll", handleUserScroll);
|
|
1966
1943
|
};
|
|
1967
|
-
|
|
1944
|
+
// This effect re-runs whenever the list size or item heights change.
|
|
1945
|
+
}, [totalCount, positions, stickToBottom]);
|
|
1968
1946
|
|
|
1969
1947
|
const scrollToBottom = useCallback(
|
|
1970
1948
|
(behavior: ScrollBehavior = "smooth") => {
|
|
@@ -1995,13 +1973,7 @@ function createProxyHandler<T>(
|
|
|
1995
1973
|
const virtualizerProps = {
|
|
1996
1974
|
outer: {
|
|
1997
1975
|
ref: containerRef,
|
|
1998
|
-
style: {
|
|
1999
|
-
overflowY: "auto" as const,
|
|
2000
|
-
height: "100%",
|
|
2001
|
-
overflowAnchor: stickToBottom
|
|
2002
|
-
? ("auto" as const)
|
|
2003
|
-
: ("none" as const),
|
|
2004
|
-
},
|
|
1976
|
+
style: { overflowY: "auto" as const, height: "100%" },
|
|
2005
1977
|
},
|
|
2006
1978
|
inner: {
|
|
2007
1979
|
style: {
|