cogsbox-state 0.5.383 → 0.5.384
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 +741 -746
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +65 -84
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,67 @@ 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,
|
|
2006
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
2023
2007
|
|
|
2024
|
-
//
|
|
2025
|
-
// This
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
// Part 1: Update the state machine with a tolerance
|
|
2008
|
+
// --- START OF MINIMAL FIX ---
|
|
2009
|
+
// This block is the only thing added. It updates the master 'status'.
|
|
2010
|
+
// This is the critical missing piece that tells the component if it should auto-scroll.
|
|
2011
|
+
// A 10px buffer prevents jittering when you are scrolled to the very bottom.
|
|
2029
2012
|
const isAtBottom =
|
|
2030
|
-
scrollHeight - scrollTop - clientHeight <
|
|
2031
|
-
bottomLockThreshold;
|
|
2013
|
+
scrollHeight - scrollTop - clientHeight < 10;
|
|
2032
2014
|
|
|
2033
2015
|
if (isAtBottom) {
|
|
2016
|
+
// If we scroll back to the bottom, re-lock.
|
|
2034
2017
|
if (status !== "LOCKED_AT_BOTTOM") {
|
|
2035
|
-
console.log(
|
|
2036
|
-
"SCROLL EVENT: Reached bottom -> LOCKED_AT_BOTTOM"
|
|
2037
|
-
);
|
|
2038
2018
|
setStatus("LOCKED_AT_BOTTOM");
|
|
2039
2019
|
}
|
|
2040
2020
|
} else {
|
|
2021
|
+
// If we have scrolled up, unlock from the bottom.
|
|
2041
2022
|
if (status !== "IDLE_NOT_AT_BOTTOM") {
|
|
2042
|
-
console.log(
|
|
2043
|
-
"SCROLL EVENT: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2044
|
-
);
|
|
2045
2023
|
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2046
2024
|
}
|
|
2047
2025
|
}
|
|
2026
|
+
// --- END OF MINIMAL FIX ---
|
|
2048
2027
|
|
|
2049
|
-
//
|
|
2028
|
+
// The rest is YOUR original, working logic for updating the visible items.
|
|
2029
|
+
if (
|
|
2030
|
+
Math.abs(scrollTop - lastUpdateAtScrollTop.current) <
|
|
2031
|
+
scrollThreshold
|
|
2032
|
+
) {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
console.log(
|
|
2037
|
+
`Threshold passed at ${scrollTop}px. Recalculating range...`
|
|
2038
|
+
);
|
|
2039
|
+
|
|
2040
|
+
// NOW we do the expensive work.
|
|
2050
2041
|
let high = totalCount - 1;
|
|
2051
2042
|
let low = 0;
|
|
2052
|
-
let
|
|
2043
|
+
let topItemIndex = 0;
|
|
2053
2044
|
while (low <= high) {
|
|
2054
2045
|
const mid = Math.floor((low + high) / 2);
|
|
2055
2046
|
if (positions[mid]! < scrollTop) {
|
|
2056
|
-
|
|
2047
|
+
topItemIndex = mid;
|
|
2057
2048
|
low = mid + 1;
|
|
2058
2049
|
} else {
|
|
2059
2050
|
high = mid - 1;
|
|
2060
2051
|
}
|
|
2061
2052
|
}
|
|
2062
2053
|
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
potentialTopIndex - overscan
|
|
2066
|
-
);
|
|
2067
|
-
|
|
2068
|
-
if (potentialStartIndex === range.startIndex) {
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
let endIndex = potentialStartIndex;
|
|
2054
|
+
const startIndex = Math.max(0, topItemIndex - overscan);
|
|
2055
|
+
let endIndex = startIndex;
|
|
2073
2056
|
const visibleEnd = scrollTop + clientHeight;
|
|
2074
2057
|
while (
|
|
2075
2058
|
endIndex < totalCount &&
|
|
@@ -2078,13 +2061,13 @@ function createProxyHandler<T>(
|
|
|
2078
2061
|
endIndex++;
|
|
2079
2062
|
}
|
|
2080
2063
|
|
|
2081
|
-
console.log(
|
|
2082
|
-
`Index changed from ${range.startIndex} to ${potentialStartIndex}. Updating range.`
|
|
2083
|
-
);
|
|
2084
2064
|
setRange({
|
|
2085
|
-
startIndex
|
|
2065
|
+
startIndex,
|
|
2086
2066
|
endIndex: Math.min(totalCount, endIndex + overscan),
|
|
2087
2067
|
});
|
|
2068
|
+
|
|
2069
|
+
// Finally, we record that we did the work at THIS scroll position.
|
|
2070
|
+
lastUpdateAtScrollTop.current = scrollTop;
|
|
2088
2071
|
};
|
|
2089
2072
|
|
|
2090
2073
|
container.addEventListener("scroll", handleUserScroll, {
|
|
@@ -2092,16 +2075,14 @@ function createProxyHandler<T>(
|
|
|
2092
2075
|
});
|
|
2093
2076
|
return () =>
|
|
2094
2077
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2095
|
-
}, [totalCount, positions,
|
|
2078
|
+
}, [totalCount, positions, itemHeight, overscan, status]);
|
|
2096
2079
|
|
|
2097
|
-
// --- (The rest of your code: scrollToBottom, scrollToIndex, virtualizerProps is fine) ---
|
|
2098
2080
|
const scrollToBottom = useCallback(() => {
|
|
2099
2081
|
console.log(
|
|
2100
2082
|
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2101
2083
|
);
|
|
2102
|
-
if (totalCount === 0) return;
|
|
2103
2084
|
setStatus("SCROLLING_TO_BOTTOM");
|
|
2104
|
-
}, [
|
|
2085
|
+
}, []);
|
|
2105
2086
|
|
|
2106
2087
|
const scrollToIndex = useCallback(
|
|
2107
2088
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|