cogsbox-state 0.5.380 → 0.5.382
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 +760 -770
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +66 -81
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
|
+
// Refs and State
|
|
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);
|
|
1831
1830
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1832
1831
|
|
|
1832
|
+
// Subscribe to external state updates for item heights
|
|
1833
1833
|
useEffect(() => {
|
|
1834
1834
|
const unsubscribe = getGlobalStore
|
|
1835
1835
|
.getState()
|
|
@@ -1845,6 +1845,7 @@ 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
|
|
1848
1849
|
const { totalHeight, positions } = useMemo(() => {
|
|
1849
1850
|
const shadowArray =
|
|
1850
1851
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
@@ -1866,7 +1867,7 @@ function createProxyHandler<T>(
|
|
|
1866
1867
|
shadowUpdateTrigger,
|
|
1867
1868
|
]);
|
|
1868
1869
|
|
|
1869
|
-
//
|
|
1870
|
+
// Memoize the virtualized data slice
|
|
1870
1871
|
const virtualState = useMemo(() => {
|
|
1871
1872
|
const start = Math.max(0, range.startIndex);
|
|
1872
1873
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1882,7 +1883,7 @@ function createProxyHandler<T>(
|
|
|
1882
1883
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1883
1884
|
|
|
1884
1885
|
// --- 1. STATE CONTROLLER ---
|
|
1885
|
-
//
|
|
1886
|
+
// Decides which state to transition TO based on data changes.
|
|
1886
1887
|
useLayoutEffect(() => {
|
|
1887
1888
|
const depsChanged = !isDeepEqual(
|
|
1888
1889
|
dependencies,
|
|
@@ -1893,9 +1894,10 @@ function createProxyHandler<T>(
|
|
|
1893
1894
|
if (depsChanged) {
|
|
1894
1895
|
console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
|
|
1895
1896
|
setStatus("IDLE_AT_TOP");
|
|
1896
|
-
return;
|
|
1897
|
+
return;
|
|
1897
1898
|
}
|
|
1898
1899
|
|
|
1900
|
+
// THIS IS THE CRITICAL CHECK: It only scrolls if new items arrive AND we are already locked.
|
|
1899
1901
|
if (
|
|
1900
1902
|
hasNewItems &&
|
|
1901
1903
|
status === "LOCKED_AT_BOTTOM" &&
|
|
@@ -1907,11 +1909,12 @@ function createProxyHandler<T>(
|
|
|
1907
1909
|
setStatus("GETTING_HEIGHTS");
|
|
1908
1910
|
}
|
|
1909
1911
|
|
|
1912
|
+
prevTotalCountRef.current = totalCount;
|
|
1910
1913
|
prevDepsRef.current = dependencies;
|
|
1911
1914
|
}, [totalCount, ...dependencies]);
|
|
1912
1915
|
|
|
1913
1916
|
// --- 2. STATE ACTION HANDLER ---
|
|
1914
|
-
//
|
|
1917
|
+
// Performs the ACTION for the current state (e.g., scrolling).
|
|
1915
1918
|
useLayoutEffect(() => {
|
|
1916
1919
|
const container = containerRef.current;
|
|
1917
1920
|
if (!container) return;
|
|
@@ -1923,22 +1926,17 @@ function createProxyHandler<T>(
|
|
|
1923
1926
|
stickToBottom &&
|
|
1924
1927
|
totalCount > 0
|
|
1925
1928
|
) {
|
|
1926
|
-
// If we just loaded a new chat, start the process.
|
|
1927
1929
|
console.log(
|
|
1928
|
-
"ACTION (IDLE_AT_TOP): Data
|
|
1930
|
+
"ACTION (IDLE_AT_TOP): Data arrived -> GETTING_HEIGHTS"
|
|
1929
1931
|
);
|
|
1930
1932
|
setStatus("GETTING_HEIGHTS");
|
|
1931
1933
|
} else if (status === "GETTING_HEIGHTS") {
|
|
1932
|
-
console.log(
|
|
1933
|
-
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1934
|
-
);
|
|
1935
|
-
|
|
1934
|
+
console.log("ACTION (GETTING_HEIGHTS): Setting range to end");
|
|
1936
1935
|
setRange({
|
|
1937
1936
|
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1938
1937
|
endIndex: totalCount,
|
|
1939
1938
|
});
|
|
1940
1939
|
|
|
1941
|
-
let intervalId: NodeJS.Timeout;
|
|
1942
1940
|
intervalId = setInterval(() => {
|
|
1943
1941
|
const lastItemIndex = totalCount - 1;
|
|
1944
1942
|
const shadowArray =
|
|
@@ -1950,50 +1948,17 @@ function createProxyHandler<T>(
|
|
|
1950
1948
|
|
|
1951
1949
|
if (lastItemHeight > 0) {
|
|
1952
1950
|
clearInterval(intervalId);
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
const smallAddition = addedItems > 0 && addedItems <= 3;
|
|
1958
|
-
|
|
1959
|
-
if (smallAddition) {
|
|
1960
|
-
// Let DOM render before measuring + scrolling
|
|
1961
|
-
requestAnimationFrame(() => {
|
|
1962
|
-
const prevBottom =
|
|
1963
|
-
positions[prevCount] ?? container.scrollHeight;
|
|
1964
|
-
const newBottom = container.scrollHeight;
|
|
1965
|
-
const delta = newBottom - prevBottom;
|
|
1966
|
-
|
|
1967
|
-
if (delta > 0) {
|
|
1968
|
-
container.scrollBy({
|
|
1969
|
-
top: delta,
|
|
1970
|
-
behavior: "smooth",
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
prevTotalCountRef.current = totalCount;
|
|
1974
|
-
|
|
1975
|
-
console.log(
|
|
1976
|
-
"ACTION (GETTING_HEIGHTS): Small addition -> LOCKED_AT_BOTTOM"
|
|
1977
|
-
);
|
|
1978
|
-
setStatus("LOCKED_AT_BOTTOM");
|
|
1979
|
-
});
|
|
1980
|
-
} else {
|
|
1981
|
-
console.log(
|
|
1982
|
-
"ACTION (GETTING_HEIGHTS): Large change -> SCROLLING_TO_BOTTOM"
|
|
1983
|
-
);
|
|
1984
|
-
setStatus("SCROLLING_TO_BOTTOM");
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1951
|
+
console.log(
|
|
1952
|
+
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1953
|
+
);
|
|
1954
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1987
1955
|
}
|
|
1988
|
-
},
|
|
1989
|
-
|
|
1990
|
-
return () => clearInterval(intervalId);
|
|
1956
|
+
}, 100);
|
|
1991
1957
|
} else if (status === "SCROLLING_TO_BOTTOM") {
|
|
1992
1958
|
console.log(
|
|
1993
1959
|
"ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
|
|
1994
1960
|
);
|
|
1995
1961
|
isProgrammaticScroll.current = true;
|
|
1996
|
-
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1997
1962
|
const scrollBehavior =
|
|
1998
1963
|
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1999
1964
|
|
|
@@ -2004,17 +1969,11 @@ function createProxyHandler<T>(
|
|
|
2004
1969
|
|
|
2005
1970
|
const timeoutId = setTimeout(
|
|
2006
1971
|
() => {
|
|
2007
|
-
console.log(
|
|
2008
|
-
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
2009
|
-
);
|
|
2010
1972
|
isProgrammaticScroll.current = false;
|
|
2011
|
-
shouldNotScroll.current = false;
|
|
2012
1973
|
setStatus("LOCKED_AT_BOTTOM");
|
|
2013
|
-
prevTotalCountRef.current = totalCount;
|
|
2014
1974
|
},
|
|
2015
1975
|
scrollBehavior === "smooth" ? 500 : 50
|
|
2016
1976
|
);
|
|
2017
|
-
|
|
2018
1977
|
return () => clearTimeout(timeoutId);
|
|
2019
1978
|
}
|
|
2020
1979
|
|
|
@@ -2023,46 +1982,67 @@ function createProxyHandler<T>(
|
|
|
2023
1982
|
};
|
|
2024
1983
|
}, [status, totalCount, positions]);
|
|
2025
1984
|
|
|
1985
|
+
// --- 3. USER INTERACTION & RANGE UPDATER (THE CORRECTED VERSION) ---
|
|
2026
1986
|
useEffect(() => {
|
|
2027
1987
|
const container = containerRef.current;
|
|
2028
1988
|
if (!container) return;
|
|
2029
1989
|
|
|
2030
|
-
const scrollThreshold = itemHeight;
|
|
2031
|
-
|
|
2032
1990
|
const handleUserScroll = () => {
|
|
2033
1991
|
if (isProgrammaticScroll.current) {
|
|
2034
1992
|
return;
|
|
2035
1993
|
}
|
|
2036
1994
|
|
|
2037
|
-
const scrollTop = container
|
|
2038
|
-
if (
|
|
2039
|
-
Math.abs(scrollTop - lastUpdateAtScrollTop.current) <
|
|
2040
|
-
scrollThreshold
|
|
2041
|
-
) {
|
|
2042
|
-
return;
|
|
2043
|
-
}
|
|
1995
|
+
const { scrollTop, clientHeight, scrollHeight } = container;
|
|
2044
1996
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
1997
|
+
// Part 1: UPDATE THE STATE MACHINE STATUS (The critical fix)
|
|
1998
|
+
// This tells the rest of the component whether we should auto-scroll on new messages.
|
|
1999
|
+
const isAtBottom =
|
|
2000
|
+
scrollHeight - scrollTop - clientHeight < 1;
|
|
2048
2001
|
|
|
2049
|
-
|
|
2050
|
-
|
|
2002
|
+
if (isAtBottom) {
|
|
2003
|
+
if (status !== "LOCKED_AT_BOTTOM") {
|
|
2004
|
+
console.log(
|
|
2005
|
+
"SCROLL EVENT: Reached bottom -> LOCKED_AT_BOTTOM"
|
|
2006
|
+
);
|
|
2007
|
+
setStatus("LOCKED_AT_BOTTOM");
|
|
2008
|
+
}
|
|
2009
|
+
} else {
|
|
2010
|
+
if (status !== "IDLE_NOT_AT_BOTTOM") {
|
|
2011
|
+
console.log(
|
|
2012
|
+
"SCROLL EVENT: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2013
|
+
);
|
|
2014
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// Part 2: EFFICIENTLY UPDATE THE RENDERED RANGE
|
|
2019
|
+
// This is the superior optimization from your first version. It only
|
|
2020
|
+
// re-renders if the actual items in view have changed.
|
|
2051
2021
|
let high = totalCount - 1;
|
|
2052
2022
|
let low = 0;
|
|
2053
|
-
let
|
|
2023
|
+
let potentialTopIndex = 0;
|
|
2054
2024
|
while (low <= high) {
|
|
2055
2025
|
const mid = Math.floor((low + high) / 2);
|
|
2056
2026
|
if (positions[mid]! < scrollTop) {
|
|
2057
|
-
|
|
2027
|
+
potentialTopIndex = mid;
|
|
2058
2028
|
low = mid + 1;
|
|
2059
2029
|
} else {
|
|
2060
2030
|
high = mid - 1;
|
|
2061
2031
|
}
|
|
2062
2032
|
}
|
|
2063
2033
|
|
|
2064
|
-
const
|
|
2065
|
-
|
|
2034
|
+
const potentialStartIndex = Math.max(
|
|
2035
|
+
0,
|
|
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;
|
|
2066
2046
|
const visibleEnd = scrollTop + clientHeight;
|
|
2067
2047
|
while (
|
|
2068
2048
|
endIndex < totalCount &&
|
|
@@ -2071,13 +2051,13 @@ function createProxyHandler<T>(
|
|
|
2071
2051
|
endIndex++;
|
|
2072
2052
|
}
|
|
2073
2053
|
|
|
2054
|
+
console.log(
|
|
2055
|
+
`Index changed from ${range.startIndex} to ${potentialStartIndex}. Updating range.`
|
|
2056
|
+
);
|
|
2074
2057
|
setRange({
|
|
2075
|
-
startIndex,
|
|
2058
|
+
startIndex: potentialStartIndex,
|
|
2076
2059
|
endIndex: Math.min(totalCount, endIndex + overscan),
|
|
2077
2060
|
});
|
|
2078
|
-
|
|
2079
|
-
// Finally, we record that we did the work at THIS scroll position.
|
|
2080
|
-
lastUpdateAtScrollTop.current = scrollTop;
|
|
2081
2061
|
};
|
|
2082
2062
|
|
|
2083
2063
|
container.addEventListener("scroll", handleUserScroll, {
|
|
@@ -2085,18 +2065,22 @@ function createProxyHandler<T>(
|
|
|
2085
2065
|
});
|
|
2086
2066
|
return () =>
|
|
2087
2067
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2088
|
-
}, [totalCount, positions,
|
|
2068
|
+
}, [totalCount, positions, status, range.startIndex]); // Dependencies are correct
|
|
2089
2069
|
|
|
2070
|
+
// --- 4. EXPOSED ACTIONS ---
|
|
2090
2071
|
const scrollToBottom = useCallback(() => {
|
|
2091
2072
|
console.log(
|
|
2092
2073
|
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2093
2074
|
);
|
|
2075
|
+
// Don't scroll if there's nothing to scroll to.
|
|
2076
|
+
if (totalCount === 0) return;
|
|
2094
2077
|
setStatus("SCROLLING_TO_BOTTOM");
|
|
2095
|
-
}, []);
|
|
2078
|
+
}, [totalCount]);
|
|
2096
2079
|
|
|
2097
2080
|
const scrollToIndex = useCallback(
|
|
2098
2081
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2099
2082
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2083
|
+
// Manually scrolling to an index means we are no longer at the bottom.
|
|
2100
2084
|
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2101
2085
|
containerRef.current.scrollTo({
|
|
2102
2086
|
top: positions[index],
|
|
@@ -2107,6 +2091,7 @@ function createProxyHandler<T>(
|
|
|
2107
2091
|
[positions]
|
|
2108
2092
|
);
|
|
2109
2093
|
|
|
2094
|
+
// --- 5. RENDER PROPS ---
|
|
2110
2095
|
const virtualizerProps = {
|
|
2111
2096
|
outer: {
|
|
2112
2097
|
ref: containerRef,
|