cogsbox-state 0.5.382 → 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 +745 -748
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +58 -53
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,9 +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
|
|
|
1832
|
-
// Subscribe to external state updates for item heights
|
|
1833
1833
|
useEffect(() => {
|
|
1834
1834
|
const unsubscribe = getGlobalStore
|
|
1835
1835
|
.getState()
|
|
@@ -1845,7 +1845,6 @@ function createProxyHandler<T>(
|
|
|
1845
1845
|
) as any[];
|
|
1846
1846
|
const totalCount = sourceArray.length;
|
|
1847
1847
|
|
|
1848
|
-
// Memoize positions and total height based on measured items
|
|
1849
1848
|
const { totalHeight, positions } = useMemo(() => {
|
|
1850
1849
|
const shadowArray =
|
|
1851
1850
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
@@ -1867,7 +1866,7 @@ function createProxyHandler<T>(
|
|
|
1867
1866
|
shadowUpdateTrigger,
|
|
1868
1867
|
]);
|
|
1869
1868
|
|
|
1870
|
-
//
|
|
1869
|
+
// THIS IS THE FULL, NON-PLACEHOLDER FUNCTION
|
|
1871
1870
|
const virtualState = useMemo(() => {
|
|
1872
1871
|
const start = Math.max(0, range.startIndex);
|
|
1873
1872
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1883,7 +1882,7 @@ function createProxyHandler<T>(
|
|
|
1883
1882
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1884
1883
|
|
|
1885
1884
|
// --- 1. STATE CONTROLLER ---
|
|
1886
|
-
//
|
|
1885
|
+
// This effect decides which state to transition TO.
|
|
1887
1886
|
useLayoutEffect(() => {
|
|
1888
1887
|
const depsChanged = !isDeepEqual(
|
|
1889
1888
|
dependencies,
|
|
@@ -1894,10 +1893,9 @@ function createProxyHandler<T>(
|
|
|
1894
1893
|
if (depsChanged) {
|
|
1895
1894
|
console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
|
|
1896
1895
|
setStatus("IDLE_AT_TOP");
|
|
1897
|
-
return;
|
|
1896
|
+
return; // Stop here, let the next effect handle the action for the new state.
|
|
1898
1897
|
}
|
|
1899
1898
|
|
|
1900
|
-
// THIS IS THE CRITICAL CHECK: It only scrolls if new items arrive AND we are already locked.
|
|
1901
1899
|
if (
|
|
1902
1900
|
hasNewItems &&
|
|
1903
1901
|
status === "LOCKED_AT_BOTTOM" &&
|
|
@@ -1914,7 +1912,7 @@ function createProxyHandler<T>(
|
|
|
1914
1912
|
}, [totalCount, ...dependencies]);
|
|
1915
1913
|
|
|
1916
1914
|
// --- 2. STATE ACTION HANDLER ---
|
|
1917
|
-
//
|
|
1915
|
+
// This effect performs the ACTION for the current state.
|
|
1918
1916
|
useLayoutEffect(() => {
|
|
1919
1917
|
const container = containerRef.current;
|
|
1920
1918
|
if (!container) return;
|
|
@@ -1926,12 +1924,15 @@ function createProxyHandler<T>(
|
|
|
1926
1924
|
stickToBottom &&
|
|
1927
1925
|
totalCount > 0
|
|
1928
1926
|
) {
|
|
1927
|
+
// If we just loaded a new chat, start the process.
|
|
1929
1928
|
console.log(
|
|
1930
|
-
"ACTION (IDLE_AT_TOP): Data arrived -> GETTING_HEIGHTS"
|
|
1929
|
+
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1931
1930
|
);
|
|
1932
1931
|
setStatus("GETTING_HEIGHTS");
|
|
1933
1932
|
} else if (status === "GETTING_HEIGHTS") {
|
|
1934
|
-
console.log(
|
|
1933
|
+
console.log(
|
|
1934
|
+
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1935
|
+
);
|
|
1935
1936
|
setRange({
|
|
1936
1937
|
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1937
1938
|
endIndex: totalCount,
|
|
@@ -1948,10 +1949,13 @@ function createProxyHandler<T>(
|
|
|
1948
1949
|
|
|
1949
1950
|
if (lastItemHeight > 0) {
|
|
1950
1951
|
clearInterval(intervalId);
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1952
|
+
if (!shouldNotScroll.current) {
|
|
1953
|
+
console.log(
|
|
1954
|
+
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1955
|
+
);
|
|
1956
|
+
|
|
1957
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1958
|
+
}
|
|
1955
1959
|
}
|
|
1956
1960
|
}, 100);
|
|
1957
1961
|
} else if (status === "SCROLLING_TO_BOTTOM") {
|
|
@@ -1959,6 +1963,7 @@ function createProxyHandler<T>(
|
|
|
1959
1963
|
"ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
|
|
1960
1964
|
);
|
|
1961
1965
|
isProgrammaticScroll.current = true;
|
|
1966
|
+
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1962
1967
|
const scrollBehavior =
|
|
1963
1968
|
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1964
1969
|
|
|
@@ -1969,11 +1974,16 @@ function createProxyHandler<T>(
|
|
|
1969
1974
|
|
|
1970
1975
|
const timeoutId = setTimeout(
|
|
1971
1976
|
() => {
|
|
1977
|
+
console.log(
|
|
1978
|
+
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
1979
|
+
);
|
|
1972
1980
|
isProgrammaticScroll.current = false;
|
|
1981
|
+
shouldNotScroll.current = false;
|
|
1973
1982
|
setStatus("LOCKED_AT_BOTTOM");
|
|
1974
1983
|
},
|
|
1975
1984
|
scrollBehavior === "smooth" ? 500 : 50
|
|
1976
1985
|
);
|
|
1986
|
+
|
|
1977
1987
|
return () => clearTimeout(timeoutId);
|
|
1978
1988
|
}
|
|
1979
1989
|
|
|
@@ -1982,67 +1992,67 @@ function createProxyHandler<T>(
|
|
|
1982
1992
|
};
|
|
1983
1993
|
}, [status, totalCount, positions]);
|
|
1984
1994
|
|
|
1985
|
-
// --- 3. USER INTERACTION & RANGE UPDATER (THE CORRECTED VERSION) ---
|
|
1986
1995
|
useEffect(() => {
|
|
1987
1996
|
const container = containerRef.current;
|
|
1988
1997
|
if (!container) return;
|
|
1989
1998
|
|
|
1999
|
+
const scrollThreshold = itemHeight;
|
|
2000
|
+
|
|
1990
2001
|
const handleUserScroll = () => {
|
|
1991
2002
|
if (isProgrammaticScroll.current) {
|
|
1992
2003
|
return;
|
|
1993
2004
|
}
|
|
1994
2005
|
|
|
1995
|
-
const { scrollTop,
|
|
2006
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
1996
2007
|
|
|
1997
|
-
//
|
|
1998
|
-
// This
|
|
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.
|
|
1999
2012
|
const isAtBottom =
|
|
2000
|
-
scrollHeight - scrollTop - clientHeight <
|
|
2013
|
+
scrollHeight - scrollTop - clientHeight < 10;
|
|
2001
2014
|
|
|
2002
2015
|
if (isAtBottom) {
|
|
2016
|
+
// If we scroll back to the bottom, re-lock.
|
|
2003
2017
|
if (status !== "LOCKED_AT_BOTTOM") {
|
|
2004
|
-
console.log(
|
|
2005
|
-
"SCROLL EVENT: Reached bottom -> LOCKED_AT_BOTTOM"
|
|
2006
|
-
);
|
|
2007
2018
|
setStatus("LOCKED_AT_BOTTOM");
|
|
2008
2019
|
}
|
|
2009
2020
|
} else {
|
|
2021
|
+
// If we have scrolled up, unlock from the bottom.
|
|
2010
2022
|
if (status !== "IDLE_NOT_AT_BOTTOM") {
|
|
2011
|
-
console.log(
|
|
2012
|
-
"SCROLL EVENT: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2013
|
-
);
|
|
2014
2023
|
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2015
2024
|
}
|
|
2016
2025
|
}
|
|
2026
|
+
// --- END OF MINIMAL FIX ---
|
|
2027
|
+
|
|
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
|
+
}
|
|
2017
2035
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2036
|
+
console.log(
|
|
2037
|
+
`Threshold passed at ${scrollTop}px. Recalculating range...`
|
|
2038
|
+
);
|
|
2039
|
+
|
|
2040
|
+
// NOW we do the expensive work.
|
|
2021
2041
|
let high = totalCount - 1;
|
|
2022
2042
|
let low = 0;
|
|
2023
|
-
let
|
|
2043
|
+
let topItemIndex = 0;
|
|
2024
2044
|
while (low <= high) {
|
|
2025
2045
|
const mid = Math.floor((low + high) / 2);
|
|
2026
2046
|
if (positions[mid]! < scrollTop) {
|
|
2027
|
-
|
|
2047
|
+
topItemIndex = mid;
|
|
2028
2048
|
low = mid + 1;
|
|
2029
2049
|
} else {
|
|
2030
2050
|
high = mid - 1;
|
|
2031
2051
|
}
|
|
2032
2052
|
}
|
|
2033
2053
|
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
potentialTopIndex - overscan
|
|
2037
|
-
);
|
|
2038
|
-
|
|
2039
|
-
// Only update state if the visible start index has actually changed.
|
|
2040
|
-
if (potentialStartIndex === range.startIndex) {
|
|
2041
|
-
return;
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
// If we must update, calculate the new end index.
|
|
2045
|
-
let endIndex = potentialStartIndex;
|
|
2054
|
+
const startIndex = Math.max(0, topItemIndex - overscan);
|
|
2055
|
+
let endIndex = startIndex;
|
|
2046
2056
|
const visibleEnd = scrollTop + clientHeight;
|
|
2047
2057
|
while (
|
|
2048
2058
|
endIndex < totalCount &&
|
|
@@ -2051,13 +2061,13 @@ function createProxyHandler<T>(
|
|
|
2051
2061
|
endIndex++;
|
|
2052
2062
|
}
|
|
2053
2063
|
|
|
2054
|
-
console.log(
|
|
2055
|
-
`Index changed from ${range.startIndex} to ${potentialStartIndex}. Updating range.`
|
|
2056
|
-
);
|
|
2057
2064
|
setRange({
|
|
2058
|
-
startIndex
|
|
2065
|
+
startIndex,
|
|
2059
2066
|
endIndex: Math.min(totalCount, endIndex + overscan),
|
|
2060
2067
|
});
|
|
2068
|
+
|
|
2069
|
+
// Finally, we record that we did the work at THIS scroll position.
|
|
2070
|
+
lastUpdateAtScrollTop.current = scrollTop;
|
|
2061
2071
|
};
|
|
2062
2072
|
|
|
2063
2073
|
container.addEventListener("scroll", handleUserScroll, {
|
|
@@ -2065,22 +2075,18 @@ function createProxyHandler<T>(
|
|
|
2065
2075
|
});
|
|
2066
2076
|
return () =>
|
|
2067
2077
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2068
|
-
}, [totalCount, positions,
|
|
2078
|
+
}, [totalCount, positions, itemHeight, overscan, status]);
|
|
2069
2079
|
|
|
2070
|
-
// --- 4. EXPOSED ACTIONS ---
|
|
2071
2080
|
const scrollToBottom = useCallback(() => {
|
|
2072
2081
|
console.log(
|
|
2073
2082
|
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2074
2083
|
);
|
|
2075
|
-
// Don't scroll if there's nothing to scroll to.
|
|
2076
|
-
if (totalCount === 0) return;
|
|
2077
2084
|
setStatus("SCROLLING_TO_BOTTOM");
|
|
2078
|
-
}, [
|
|
2085
|
+
}, []);
|
|
2079
2086
|
|
|
2080
2087
|
const scrollToIndex = useCallback(
|
|
2081
2088
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2082
2089
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2083
|
-
// Manually scrolling to an index means we are no longer at the bottom.
|
|
2084
2090
|
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2085
2091
|
containerRef.current.scrollTo({
|
|
2086
2092
|
top: positions[index],
|
|
@@ -2091,7 +2097,6 @@ function createProxyHandler<T>(
|
|
|
2091
2097
|
[positions]
|
|
2092
2098
|
);
|
|
2093
2099
|
|
|
2094
|
-
// --- 5. RENDER PROPS ---
|
|
2095
2100
|
const virtualizerProps = {
|
|
2096
2101
|
outer: {
|
|
2097
2102
|
ref: containerRef,
|