cogsbox-state 0.5.367 → 0.5.369
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 +663 -686
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +66 -112
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,22 +1809,18 @@ function createProxyHandler<T>(
|
|
|
1809
1809
|
dependencies = [],
|
|
1810
1810
|
} = options;
|
|
1811
1811
|
|
|
1812
|
-
// YOUR STATE MACHINE STATES
|
|
1813
1812
|
type Status =
|
|
1814
|
-
| "
|
|
1815
|
-
| "
|
|
1816
|
-
| "SCROLLING_TO_BOTTOM" // Performing the scroll animation
|
|
1817
|
-
| "LOCKED_AT_BOTTOM" // Idle at the bottom, waiting for new messages
|
|
1818
|
-
| "IDLE_NOT_AT_BOTTOM"; // User has scrolled up and broken the lock
|
|
1813
|
+
| "IDLE" // User is in control, or we are resting at the bottom.
|
|
1814
|
+
| "SCROLLING_TO_BOTTOM"; // Performing a scroll animation.
|
|
1819
1815
|
|
|
1820
1816
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1821
1817
|
const [range, setRange] = useState({
|
|
1822
1818
|
startIndex: 0,
|
|
1823
1819
|
endIndex: 10,
|
|
1824
1820
|
});
|
|
1825
|
-
const [status, setStatus] = useState<Status>("
|
|
1826
|
-
|
|
1821
|
+
const [status, setStatus] = useState<Status>("IDLE");
|
|
1827
1822
|
const prevTotalCountRef = useRef(0);
|
|
1823
|
+
|
|
1828
1824
|
const prevDepsRef = useRef(dependencies);
|
|
1829
1825
|
|
|
1830
1826
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
@@ -1879,136 +1875,82 @@ function createProxyHandler<T>(
|
|
|
1879
1875
|
});
|
|
1880
1876
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1881
1877
|
|
|
1882
|
-
// ---
|
|
1883
|
-
// This effect decides which state to transition TO.
|
|
1878
|
+
// --- Main Controller Effect ---
|
|
1884
1879
|
useLayoutEffect(() => {
|
|
1880
|
+
const container = containerRef.current;
|
|
1881
|
+
if (!container) return;
|
|
1882
|
+
|
|
1885
1883
|
const depsChanged = !isDeepEqual(
|
|
1886
1884
|
dependencies,
|
|
1887
1885
|
prevDepsRef.current
|
|
1888
1886
|
);
|
|
1889
1887
|
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1890
1888
|
|
|
1889
|
+
// 1. On Chat Change: Reset to a clean state and trigger an instant scroll.
|
|
1891
1890
|
if (depsChanged) {
|
|
1892
|
-
console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
|
|
1893
|
-
setStatus("IDLE_AT_TOP");
|
|
1894
|
-
return; // Stop here, let the next effect handle the action for the new state.
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
if (
|
|
1898
|
-
hasNewItems &&
|
|
1899
|
-
status === "LOCKED_AT_BOTTOM" &&
|
|
1900
|
-
stickToBottom
|
|
1901
|
-
) {
|
|
1902
1891
|
console.log(
|
|
1903
|
-
"
|
|
1892
|
+
"EVENT: Chat changed. Resetting state and scrolling."
|
|
1904
1893
|
);
|
|
1905
|
-
setStatus("
|
|
1894
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1895
|
+
// This return is important. It lets the effect re-run with the new status.
|
|
1896
|
+
return;
|
|
1906
1897
|
}
|
|
1907
1898
|
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1899
|
+
// 2. On New Message: Check if we SHOULD scroll.
|
|
1900
|
+
// This will only happen if we are IDLE (meaning we are already at the bottom).
|
|
1901
|
+
if (hasNewItems && status === "IDLE") {
|
|
1902
|
+
const isAtBottom =
|
|
1903
|
+
container.scrollHeight -
|
|
1904
|
+
container.scrollTop -
|
|
1905
|
+
container.clientHeight <
|
|
1906
|
+
itemHeight;
|
|
1907
|
+
if (isAtBottom && stickToBottom) {
|
|
1908
|
+
console.log(
|
|
1909
|
+
"EVENT: New message arrived while at bottom. -> SCROLLING_TO_BOTTOM"
|
|
1910
|
+
);
|
|
1911
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1912
|
+
return; // Let the effect re-run with the new status.
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1919
1915
|
|
|
1920
|
-
|
|
1921
|
-
status === "IDLE_AT_TOP" &&
|
|
1922
|
-
stickToBottom &&
|
|
1923
|
-
totalCount > 0
|
|
1924
|
-
) {
|
|
1925
|
-
// If we just loaded a new chat, start the process.
|
|
1926
|
-
console.log(
|
|
1927
|
-
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1928
|
-
);
|
|
1929
|
-
setStatus("GETTING_HEIGHTS");
|
|
1930
|
-
} else if (status === "GETTING_HEIGHTS") {
|
|
1931
|
-
console.log(
|
|
1932
|
-
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1933
|
-
);
|
|
1934
|
-
setRange({
|
|
1935
|
-
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1936
|
-
endIndex: totalCount,
|
|
1937
|
-
});
|
|
1916
|
+
let scrollTimeoutId: NodeJS.Timeout | undefined;
|
|
1938
1917
|
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
const lastItemHeight =
|
|
1946
|
-
shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
|
|
1947
|
-
|
|
1948
|
-
if (lastItemHeight > 0) {
|
|
1949
|
-
clearInterval(intervalId);
|
|
1950
|
-
console.log(
|
|
1951
|
-
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1952
|
-
);
|
|
1953
|
-
setStatus("SCROLLING_TO_BOTTOM");
|
|
1954
|
-
}
|
|
1955
|
-
}, 100);
|
|
1956
|
-
} else if (status === "SCROLLING_TO_BOTTOM") {
|
|
1918
|
+
// 3. If we are in the SCROLLING state, perform the scroll action.
|
|
1919
|
+
if (status === "SCROLLING_TO_BOTTOM") {
|
|
1920
|
+
// A chat change or initial load should be instant. New messages should be smooth.
|
|
1921
|
+
const isInitial =
|
|
1922
|
+
prevTotalCountRef.current === 0 || depsChanged;
|
|
1923
|
+
const scrollBehavior = isInitial ? "auto" : "smooth";
|
|
1957
1924
|
console.log(
|
|
1958
|
-
|
|
1925
|
+
`ACTION: Scrolling to bottom. Behavior: ${scrollBehavior}`
|
|
1959
1926
|
);
|
|
1960
|
-
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1961
|
-
const scrollBehavior =
|
|
1962
|
-
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1963
1927
|
|
|
1964
1928
|
container.scrollTo({
|
|
1965
1929
|
top: container.scrollHeight,
|
|
1966
1930
|
behavior: scrollBehavior,
|
|
1967
1931
|
});
|
|
1968
1932
|
|
|
1969
|
-
|
|
1933
|
+
// After the scroll, transition back to IDLE.
|
|
1934
|
+
// The timeout lets the animation finish so this doesn't happen too early.
|
|
1935
|
+
scrollTimeoutId = setTimeout(
|
|
1970
1936
|
() => {
|
|
1971
|
-
console.log(
|
|
1972
|
-
|
|
1973
|
-
);
|
|
1974
|
-
setStatus("LOCKED_AT_BOTTOM");
|
|
1937
|
+
console.log("ACTION: Scroll finished. -> IDLE");
|
|
1938
|
+
setStatus("IDLE");
|
|
1975
1939
|
},
|
|
1976
|
-
|
|
1940
|
+
isInitial ? 50 : 500
|
|
1977
1941
|
);
|
|
1978
|
-
|
|
1979
|
-
return () => clearTimeout(timeoutId);
|
|
1980
1942
|
}
|
|
1981
1943
|
|
|
1982
|
-
//
|
|
1983
|
-
// The scroll has either finished or been disabled by the user.
|
|
1984
|
-
|
|
1985
|
-
return () => {
|
|
1986
|
-
if (intervalId) clearInterval(intervalId);
|
|
1987
|
-
};
|
|
1988
|
-
}, [status, totalCount, positions]);
|
|
1989
|
-
|
|
1990
|
-
// --- 3. USER INTERACTION & RANGE UPDATER ---
|
|
1991
|
-
useEffect(() => {
|
|
1992
|
-
const container = containerRef.current;
|
|
1993
|
-
if (!container) return;
|
|
1994
|
-
|
|
1944
|
+
// --- User Scroll Handling & Range Update ---
|
|
1995
1945
|
const handleUserScroll = () => {
|
|
1996
|
-
//
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
container.clientHeight <
|
|
2002
|
-
1;
|
|
2003
|
-
if (!isAtBottom) {
|
|
2004
|
-
console.log(
|
|
2005
|
-
"USER ACTION: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2006
|
-
);
|
|
2007
|
-
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2008
|
-
}
|
|
1946
|
+
// If the user scrolls, they are in control. Immediately go to IDLE.
|
|
1947
|
+
// This will also cancel any pending scroll-end timeouts.
|
|
1948
|
+
if (status === "SCROLLING_TO_BOTTOM") {
|
|
1949
|
+
console.log("USER ACTION: Interrupted scroll. -> IDLE");
|
|
1950
|
+
setStatus("IDLE");
|
|
2009
1951
|
}
|
|
2010
|
-
|
|
2011
|
-
// This is the full
|
|
1952
|
+
|
|
1953
|
+
// This is the full range update function.
|
|
2012
1954
|
const { scrollTop, clientHeight } = container;
|
|
2013
1955
|
let low = 0,
|
|
2014
1956
|
high = totalCount - 1;
|
|
@@ -2035,9 +1977,21 @@ function createProxyHandler<T>(
|
|
|
2035
1977
|
container.addEventListener("scroll", handleUserScroll, {
|
|
2036
1978
|
passive: true,
|
|
2037
1979
|
});
|
|
2038
|
-
|
|
1980
|
+
|
|
1981
|
+
// Always update range on render if we are IDLE.
|
|
1982
|
+
if (status === "IDLE") {
|
|
1983
|
+
handleUserScroll();
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// Update refs for the next render cycle.
|
|
1987
|
+
prevTotalCountRef.current = totalCount;
|
|
1988
|
+
prevDepsRef.current = dependencies;
|
|
1989
|
+
|
|
1990
|
+
return () => {
|
|
2039
1991
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2040
|
-
|
|
1992
|
+
if (scrollTimeoutId) clearTimeout(scrollTimeoutId);
|
|
1993
|
+
};
|
|
1994
|
+
}, [totalCount, positions, status, ...dependencies]);
|
|
2041
1995
|
|
|
2042
1996
|
const scrollToBottom = useCallback(() => {
|
|
2043
1997
|
console.log(
|
|
@@ -2049,7 +2003,7 @@ function createProxyHandler<T>(
|
|
|
2049
2003
|
const scrollToIndex = useCallback(
|
|
2050
2004
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2051
2005
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2052
|
-
setStatus("
|
|
2006
|
+
setStatus("IDLE");
|
|
2053
2007
|
containerRef.current.scrollTo({
|
|
2054
2008
|
top: positions[index],
|
|
2055
2009
|
behavior,
|