cogsbox-state 0.5.342 → 0.5.344
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 +460 -466
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +62 -65
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1804,19 +1804,27 @@ function createProxyHandler<T>(
|
|
|
1804
1804
|
): VirtualStateObjectResult<any[]> => {
|
|
1805
1805
|
const {
|
|
1806
1806
|
itemHeight = 50,
|
|
1807
|
-
overscan = 6,
|
|
1807
|
+
overscan = 6,
|
|
1808
1808
|
stickToBottom = false,
|
|
1809
1809
|
} = options;
|
|
1810
1810
|
|
|
1811
1811
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1812
|
+
// We'll set the range to the end first, then let an effect handle the scroll.
|
|
1813
|
+
const initialRange = () => {
|
|
1814
|
+
if (stickToBottom) {
|
|
1815
|
+
const visibleCount = 10; // A reasonable guess for initial render
|
|
1816
|
+
return {
|
|
1817
|
+
startIndex: Math.max(
|
|
1818
|
+
0,
|
|
1819
|
+
sourceArray.length - visibleCount - overscan
|
|
1820
|
+
),
|
|
1821
|
+
endIndex: sourceArray.length,
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
return { startIndex: 0, endIndex: 10 };
|
|
1825
|
+
};
|
|
1826
|
+
const [range, setRange] = useState(initialRange);
|
|
1817
1827
|
const isLockedToBottomRef = useRef(stickToBottom);
|
|
1818
|
-
// Store the previous item count to detect when new items are added.
|
|
1819
|
-
const prevTotalCountRef = useRef(0);
|
|
1820
1828
|
|
|
1821
1829
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1822
1830
|
|
|
@@ -1870,16 +1878,48 @@ function createProxyHandler<T>(
|
|
|
1870
1878
|
});
|
|
1871
1879
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1872
1880
|
|
|
1873
|
-
// This
|
|
1881
|
+
// This is the implementation of YOUR ALGORITHM.
|
|
1874
1882
|
useLayoutEffect(() => {
|
|
1875
1883
|
const container = containerRef.current;
|
|
1876
|
-
if (
|
|
1884
|
+
if (
|
|
1885
|
+
!container ||
|
|
1886
|
+
!stickToBottom ||
|
|
1887
|
+
!isLockedToBottomRef.current
|
|
1888
|
+
) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1877
1891
|
|
|
1878
|
-
|
|
1892
|
+
// STEP 1: Check if the last item is measured. This is our "ready" signal.
|
|
1893
|
+
const lastItemIndex = totalCount - 1;
|
|
1894
|
+
const shadowArray =
|
|
1895
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
1896
|
+
[];
|
|
1897
|
+
const lastItemIsMeasured =
|
|
1898
|
+
lastItemIndex >= 0 &&
|
|
1899
|
+
shadowArray[lastItemIndex]?.virtualizer?.itemHeight > 0;
|
|
1900
|
+
|
|
1901
|
+
// STEP 2: If it's measured, we know totalHeight is correct. We can now scroll.
|
|
1902
|
+
if (lastItemIsMeasured || totalCount === 0) {
|
|
1903
|
+
// A timeout is essential for 'smooth' to work reliably after a render.
|
|
1904
|
+
const scrollTimeout = setTimeout(() => {
|
|
1905
|
+
container.scrollTo({
|
|
1906
|
+
top: container.scrollHeight,
|
|
1907
|
+
behavior: "smooth",
|
|
1908
|
+
});
|
|
1909
|
+
}, 50); // A small buffer is safer than 0ms.
|
|
1910
|
+
|
|
1911
|
+
return () => clearTimeout(scrollTimeout);
|
|
1912
|
+
}
|
|
1913
|
+
// If the last item is NOT measured, this effect does nothing and simply waits.
|
|
1914
|
+
// It will automatically re-run when the measurement comes in (via shadowUpdateTrigger).
|
|
1915
|
+
}, [totalCount, totalHeight, stickToBottom]); // Re-run when layout changes.
|
|
1916
|
+
|
|
1917
|
+
// This effect ONLY handles user interaction and range updates.
|
|
1918
|
+
useEffect(() => {
|
|
1919
|
+
const container = containerRef.current;
|
|
1920
|
+
if (!container) return;
|
|
1879
1921
|
|
|
1880
|
-
// This function determines what's visible in the viewport.
|
|
1881
1922
|
const updateVirtualRange = () => {
|
|
1882
|
-
if (!container) return;
|
|
1883
1923
|
const { scrollTop, clientHeight } = container;
|
|
1884
1924
|
let low = 0,
|
|
1885
1925
|
high = totalCount - 1;
|
|
@@ -1897,53 +1937,13 @@ function createProxyHandler<T>(
|
|
|
1897
1937
|
) {
|
|
1898
1938
|
endIndex++;
|
|
1899
1939
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1940
|
+
setRange({
|
|
1941
|
+
startIndex,
|
|
1942
|
+
endIndex: Math.min(totalCount, endIndex + overscan),
|
|
1943
|
+
});
|
|
1902
1944
|
};
|
|
1903
1945
|
|
|
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
|
-
|
|
1944
1946
|
const handleUserScroll = () => {
|
|
1945
|
-
if (!container) return;
|
|
1946
|
-
// If the user scrolls up, break the lock.
|
|
1947
1947
|
const isAtBottom =
|
|
1948
1948
|
container.scrollHeight -
|
|
1949
1949
|
container.scrollTop -
|
|
@@ -1952,24 +1952,22 @@ function createProxyHandler<T>(
|
|
|
1952
1952
|
if (!isAtBottom) {
|
|
1953
1953
|
isLockedToBottomRef.current = false;
|
|
1954
1954
|
}
|
|
1955
|
+
updateVirtualRange();
|
|
1955
1956
|
};
|
|
1956
1957
|
|
|
1957
1958
|
container.addEventListener("scroll", handleUserScroll, {
|
|
1958
1959
|
passive: true,
|
|
1959
1960
|
});
|
|
1961
|
+
// Initial range calculation
|
|
1962
|
+
updateVirtualRange();
|
|
1963
|
+
|
|
1960
1964
|
return () =>
|
|
1961
1965
|
container.removeEventListener("scroll", handleUserScroll);
|
|
1962
|
-
}, []);
|
|
1963
|
-
|
|
1964
|
-
// After every render, update the previous count ref.
|
|
1965
|
-
useEffect(() => {
|
|
1966
|
-
prevTotalCountRef.current = totalCount;
|
|
1967
|
-
});
|
|
1966
|
+
}, [totalCount, positions]);
|
|
1968
1967
|
|
|
1969
1968
|
const scrollToBottom = useCallback(
|
|
1970
1969
|
(behavior: ScrollBehavior = "smooth") => {
|
|
1971
1970
|
if (containerRef.current) {
|
|
1972
|
-
// Re-enable the lock when the user explicitly asks to scroll to bottom.
|
|
1973
1971
|
isLockedToBottomRef.current = true;
|
|
1974
1972
|
containerRef.current.scrollTo({
|
|
1975
1973
|
top: containerRef.current.scrollHeight,
|
|
@@ -1983,7 +1981,6 @@ function createProxyHandler<T>(
|
|
|
1983
1981
|
const scrollToIndex = useCallback(
|
|
1984
1982
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1985
1983
|
if (containerRef.current && positions[index] !== undefined) {
|
|
1986
|
-
// Scrolling to a specific index should break the lock.
|
|
1987
1984
|
isLockedToBottomRef.current = false;
|
|
1988
1985
|
containerRef.current.scrollTo({
|
|
1989
1986
|
top: positions[index],
|