cogsbox-state 0.5.383 → 0.5.385
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 +644 -642
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +72 -86
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1817,7 +1817,7 @@ function createProxyHandler<T>(
|
|
|
1817
1817
|
| "LOCKED_AT_BOTTOM"
|
|
1818
1818
|
| "IDLE_NOT_AT_BOTTOM";
|
|
1819
1819
|
|
|
1820
|
-
|
|
1820
|
+
const shouldNotScroll = useRef(false);
|
|
1821
1821
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1822
1822
|
const [range, setRange] = useState({
|
|
1823
1823
|
startIndex: 0,
|
|
@@ -1827,15 +1827,9 @@ function createProxyHandler<T>(
|
|
|
1827
1827
|
const isProgrammaticScroll = useRef(false);
|
|
1828
1828
|
const prevTotalCountRef = useRef(0);
|
|
1829
1829
|
const prevDepsRef = useRef(dependencies);
|
|
1830
|
+
const lastUpdateAtScrollTop = useRef(0);
|
|
1830
1831
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1831
|
-
|
|
1832
|
-
// This is the key to preventing the jump when scrolled up.
|
|
1833
|
-
const scrollAnchorRef = useRef<{
|
|
1834
|
-
scrollTop: number;
|
|
1835
|
-
scrollHeight: number;
|
|
1836
|
-
} | null>(null);
|
|
1837
|
-
|
|
1838
|
-
// ... (Your existing useEffect for shadow state and useMemo for data are fine) ...
|
|
1832
|
+
|
|
1839
1833
|
useEffect(() => {
|
|
1840
1834
|
const unsubscribe = getGlobalStore
|
|
1841
1835
|
.getState()
|
|
@@ -1872,6 +1866,7 @@ function createProxyHandler<T>(
|
|
|
1872
1866
|
shadowUpdateTrigger,
|
|
1873
1867
|
]);
|
|
1874
1868
|
|
|
1869
|
+
// THIS IS THE FULL, NON-PLACEHOLDER FUNCTION
|
|
1875
1870
|
const virtualState = useMemo(() => {
|
|
1876
1871
|
const start = Math.max(0, range.startIndex);
|
|
1877
1872
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1886,28 +1881,22 @@ function createProxyHandler<T>(
|
|
|
1886
1881
|
});
|
|
1887
1882
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1888
1883
|
|
|
1889
|
-
// --- 1. STATE CONTROLLER
|
|
1884
|
+
// --- 1. STATE CONTROLLER ---
|
|
1885
|
+
// This effect decides which state to transition TO.
|
|
1890
1886
|
useLayoutEffect(() => {
|
|
1891
|
-
const container = containerRef.current;
|
|
1892
|
-
if (!container) return;
|
|
1893
|
-
|
|
1894
|
-
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1895
1887
|
const depsChanged = !isDeepEqual(
|
|
1896
1888
|
dependencies,
|
|
1897
1889
|
prevDepsRef.current
|
|
1898
1890
|
);
|
|
1891
|
+
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1899
1892
|
|
|
1900
|
-
// Condition 1: Hard Reset.
|
|
1901
|
-
// This happens when you load a completely new list (e.g., switch chats).
|
|
1902
1893
|
if (depsChanged) {
|
|
1903
|
-
console.log(
|
|
1904
|
-
"TRANSITION (Hard Reset): Deps changed -> IDLE_AT_TOP"
|
|
1905
|
-
);
|
|
1894
|
+
console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
|
|
1906
1895
|
setStatus("IDLE_AT_TOP");
|
|
1907
|
-
|
|
1896
|
+
return; // Stop here, let the next effect handle the action for the new state.
|
|
1908
1897
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1898
|
+
|
|
1899
|
+
if (
|
|
1911
1900
|
hasNewItems &&
|
|
1912
1901
|
status === "LOCKED_AT_BOTTOM" &&
|
|
1913
1902
|
stickToBottom
|
|
@@ -1917,29 +1906,14 @@ function createProxyHandler<T>(
|
|
|
1917
1906
|
);
|
|
1918
1907
|
setStatus("GETTING_HEIGHTS");
|
|
1919
1908
|
}
|
|
1920
|
-
// CHANGE: Condition 3: New items arrive while we are scrolled up.
|
|
1921
|
-
// This is the "scroll anchoring" logic that prevents the jump.
|
|
1922
|
-
else if (hasNewItems && scrollAnchorRef.current) {
|
|
1923
|
-
console.log(
|
|
1924
|
-
"ACTION: Maintaining scroll position after new items added."
|
|
1925
|
-
);
|
|
1926
|
-
// We adjust the scroll position by the amount of height that was just added.
|
|
1927
|
-
container.scrollTop =
|
|
1928
|
-
scrollAnchorRef.current.scrollTop +
|
|
1929
|
-
(container.scrollHeight -
|
|
1930
|
-
scrollAnchorRef.current.scrollHeight);
|
|
1931
|
-
}
|
|
1932
1909
|
|
|
1933
|
-
// Finally, update the refs for the next render.
|
|
1934
1910
|
prevTotalCountRef.current = totalCount;
|
|
1935
1911
|
prevDepsRef.current = dependencies;
|
|
1936
|
-
|
|
1937
|
-
scrollAnchorRef.current = null;
|
|
1938
|
-
}, [totalCount, ...dependencies]); // This dependency array is correct.
|
|
1912
|
+
}, [totalCount, ...dependencies]);
|
|
1939
1913
|
|
|
1940
|
-
// --- 2. STATE ACTION HANDLER
|
|
1914
|
+
// --- 2. STATE ACTION HANDLER ---
|
|
1915
|
+
// This effect performs the ACTION for the current state.
|
|
1941
1916
|
useLayoutEffect(() => {
|
|
1942
|
-
// ... (This effect's logic for GETTING_HEIGHTS and SCROLLING_TO_BOTTOM is correct and can remain the same)
|
|
1943
1917
|
const container = containerRef.current;
|
|
1944
1918
|
if (!container) return;
|
|
1945
1919
|
|
|
@@ -1950,12 +1924,15 @@ function createProxyHandler<T>(
|
|
|
1950
1924
|
stickToBottom &&
|
|
1951
1925
|
totalCount > 0
|
|
1952
1926
|
) {
|
|
1927
|
+
// If we just loaded a new chat, start the process.
|
|
1953
1928
|
console.log(
|
|
1954
|
-
"ACTION (IDLE_AT_TOP): Data arrived -> GETTING_HEIGHTS"
|
|
1929
|
+
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1955
1930
|
);
|
|
1956
1931
|
setStatus("GETTING_HEIGHTS");
|
|
1957
1932
|
} else if (status === "GETTING_HEIGHTS") {
|
|
1958
|
-
console.log(
|
|
1933
|
+
console.log(
|
|
1934
|
+
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1935
|
+
);
|
|
1959
1936
|
setRange({
|
|
1960
1937
|
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1961
1938
|
endIndex: totalCount,
|
|
@@ -1972,10 +1949,13 @@ function createProxyHandler<T>(
|
|
|
1972
1949
|
|
|
1973
1950
|
if (lastItemHeight > 0) {
|
|
1974
1951
|
clearInterval(intervalId);
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1952
|
+
if (!shouldNotScroll.current) {
|
|
1953
|
+
console.log(
|
|
1954
|
+
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1955
|
+
);
|
|
1956
|
+
|
|
1957
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1958
|
+
}
|
|
1979
1959
|
}
|
|
1980
1960
|
}, 100);
|
|
1981
1961
|
} else if (status === "SCROLLING_TO_BOTTOM") {
|
|
@@ -1983,6 +1963,7 @@ function createProxyHandler<T>(
|
|
|
1983
1963
|
"ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
|
|
1984
1964
|
);
|
|
1985
1965
|
isProgrammaticScroll.current = true;
|
|
1966
|
+
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1986
1967
|
const scrollBehavior =
|
|
1987
1968
|
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1988
1969
|
|
|
@@ -1993,11 +1974,16 @@ function createProxyHandler<T>(
|
|
|
1993
1974
|
|
|
1994
1975
|
const timeoutId = setTimeout(
|
|
1995
1976
|
() => {
|
|
1977
|
+
console.log(
|
|
1978
|
+
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
1979
|
+
);
|
|
1996
1980
|
isProgrammaticScroll.current = false;
|
|
1981
|
+
shouldNotScroll.current = false;
|
|
1997
1982
|
setStatus("LOCKED_AT_BOTTOM");
|
|
1998
1983
|
},
|
|
1999
1984
|
scrollBehavior === "smooth" ? 500 : 50
|
|
2000
1985
|
);
|
|
1986
|
+
|
|
2001
1987
|
return () => clearTimeout(timeoutId);
|
|
2002
1988
|
}
|
|
2003
1989
|
|
|
@@ -2006,70 +1992,72 @@ function createProxyHandler<T>(
|
|
|
2006
1992
|
};
|
|
2007
1993
|
}, [status, totalCount, positions]);
|
|
2008
1994
|
|
|
2009
|
-
// --- 3. USER INTERACTION & RANGE UPDATER (REVISED) ---
|
|
2010
1995
|
useEffect(() => {
|
|
2011
1996
|
const container = containerRef.current;
|
|
2012
1997
|
if (!container) return;
|
|
2013
1998
|
|
|
2014
|
-
|
|
2015
|
-
const bottomLockThreshold = 10; // 10px buffer
|
|
1999
|
+
const scrollThreshold = itemHeight;
|
|
2016
2000
|
|
|
2017
2001
|
const handleUserScroll = () => {
|
|
2018
2002
|
if (isProgrammaticScroll.current) {
|
|
2019
2003
|
return;
|
|
2020
2004
|
}
|
|
2021
2005
|
|
|
2022
|
-
const { scrollTop,
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2006
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
2007
|
+
console.log(
|
|
2008
|
+
"scrollTop, scrollHeight, clientHeight ",
|
|
2009
|
+
scrollTop,
|
|
2010
|
+
scrollHeight,
|
|
2011
|
+
clientHeight
|
|
2012
|
+
);
|
|
2013
|
+
// --- START OF MINIMAL FIX ---
|
|
2014
|
+
// This block is the only thing added. It updates the master 'status'.
|
|
2015
|
+
// This is the critical missing piece that tells the component if it should auto-scroll.
|
|
2016
|
+
// A 10px buffer prevents jittering when you are scrolled to the very bottom.
|
|
2029
2017
|
const isAtBottom =
|
|
2030
|
-
scrollHeight - scrollTop - clientHeight <
|
|
2031
|
-
|
|
2032
|
-
|
|
2018
|
+
scrollHeight - scrollTop - clientHeight < 10;
|
|
2019
|
+
console.log("isAtBottom", isAtBottom);
|
|
2033
2020
|
if (isAtBottom) {
|
|
2021
|
+
// If we scroll back to the bottom, re-lock.
|
|
2034
2022
|
if (status !== "LOCKED_AT_BOTTOM") {
|
|
2035
|
-
console.log(
|
|
2036
|
-
"SCROLL EVENT: Reached bottom -> LOCKED_AT_BOTTOM"
|
|
2037
|
-
);
|
|
2038
2023
|
setStatus("LOCKED_AT_BOTTOM");
|
|
2039
2024
|
}
|
|
2040
2025
|
} else {
|
|
2026
|
+
// If we have scrolled up, unlock from the bottom.
|
|
2041
2027
|
if (status !== "IDLE_NOT_AT_BOTTOM") {
|
|
2042
|
-
console.log(
|
|
2043
|
-
"SCROLL EVENT: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2044
|
-
);
|
|
2045
2028
|
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2046
2029
|
}
|
|
2047
2030
|
}
|
|
2031
|
+
// --- END OF MINIMAL FIX ---
|
|
2032
|
+
|
|
2033
|
+
// The rest is YOUR original, working logic for updating the visible items.
|
|
2034
|
+
if (
|
|
2035
|
+
Math.abs(scrollTop - lastUpdateAtScrollTop.current) <
|
|
2036
|
+
scrollThreshold
|
|
2037
|
+
) {
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2048
2040
|
|
|
2049
|
-
|
|
2041
|
+
console.log(
|
|
2042
|
+
`Threshold passed at ${scrollTop}px. Recalculating range...`
|
|
2043
|
+
);
|
|
2044
|
+
|
|
2045
|
+
// NOW we do the expensive work.
|
|
2050
2046
|
let high = totalCount - 1;
|
|
2051
2047
|
let low = 0;
|
|
2052
|
-
let
|
|
2048
|
+
let topItemIndex = 0;
|
|
2053
2049
|
while (low <= high) {
|
|
2054
2050
|
const mid = Math.floor((low + high) / 2);
|
|
2055
2051
|
if (positions[mid]! < scrollTop) {
|
|
2056
|
-
|
|
2052
|
+
topItemIndex = mid;
|
|
2057
2053
|
low = mid + 1;
|
|
2058
2054
|
} else {
|
|
2059
2055
|
high = mid - 1;
|
|
2060
2056
|
}
|
|
2061
2057
|
}
|
|
2062
2058
|
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
potentialTopIndex - overscan
|
|
2066
|
-
);
|
|
2067
|
-
|
|
2068
|
-
if (potentialStartIndex === range.startIndex) {
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
let endIndex = potentialStartIndex;
|
|
2059
|
+
const startIndex = Math.max(0, topItemIndex - overscan);
|
|
2060
|
+
let endIndex = startIndex;
|
|
2073
2061
|
const visibleEnd = scrollTop + clientHeight;
|
|
2074
2062
|
while (
|
|
2075
2063
|
endIndex < totalCount &&
|
|
@@ -2078,13 +2066,13 @@ function createProxyHandler<T>(
|
|
|
2078
2066
|
endIndex++;
|
|
2079
2067
|
}
|
|
2080
2068
|
|
|
2081
|
-
console.log(
|
|
2082
|
-
`Index changed from ${range.startIndex} to ${potentialStartIndex}. Updating range.`
|
|
2083
|
-
);
|
|
2084
2069
|
setRange({
|
|
2085
|
-
startIndex
|
|
2070
|
+
startIndex,
|
|
2086
2071
|
endIndex: Math.min(totalCount, endIndex + overscan),
|
|
2087
2072
|
});
|
|
2073
|
+
|
|
2074
|
+
// Finally, we record that we did the work at THIS scroll position.
|
|
2075
|
+
lastUpdateAtScrollTop.current = scrollTop;
|
|
2088
2076
|
};
|
|
2089
2077
|
|
|
2090
2078
|
container.addEventListener("scroll", handleUserScroll, {
|
|
@@ -2092,16 +2080,14 @@ function createProxyHandler<T>(
|
|
|
2092
2080
|
});
|
|
2093
2081
|
return () =>
|
|
2094
2082
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2095
|
-
}, [totalCount, positions,
|
|
2083
|
+
}, [totalCount, positions, itemHeight, overscan, status]);
|
|
2096
2084
|
|
|
2097
|
-
// --- (The rest of your code: scrollToBottom, scrollToIndex, virtualizerProps is fine) ---
|
|
2098
2085
|
const scrollToBottom = useCallback(() => {
|
|
2099
2086
|
console.log(
|
|
2100
2087
|
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2101
2088
|
);
|
|
2102
|
-
if (totalCount === 0) return;
|
|
2103
2089
|
setStatus("SCROLLING_TO_BOTTOM");
|
|
2104
|
-
}, [
|
|
2090
|
+
}, []);
|
|
2105
2091
|
|
|
2106
2092
|
const scrollToIndex = useCallback(
|
|
2107
2093
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|