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